3500 lines
154 KiB
PHP
3500 lines
154 KiB
PHP
<?php
|
||
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
|
||
|
||
// 包含必要的文件
|
||
include 'header.php';
|
||
include 'menu.php';
|
||
include 'common-js.php';
|
||
include 'table-js.php';
|
||
|
||
// 获取插件选项
|
||
$options = Typecho_Widget::widget('Widget_Options');
|
||
$pluginOptions = $options->plugin('UrlNav');
|
||
$pageSize = isset($pluginOptions->pageSize) ? intval($pluginOptions->pageSize) : 20;
|
||
|
||
// 获取当前页码和分类
|
||
$currentPage = isset($_GET['page']) ? intval($_GET['page']) : 1;
|
||
$currentCategory = isset($_GET['category']) ? $_GET['category'] : '';
|
||
$currentStatus = isset($_GET['status']) ? $_GET['status'] : ''; // 新增:状态参数
|
||
$currentHasRss = isset($_GET['has_rss']) ? $_GET['has_rss'] : ''; // 新增:RSS筛选参数
|
||
$searchKeyword = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||
|
||
// 获取当前星级筛选参数
|
||
$currentStarRating = isset($_GET['star_rating']) ? $_GET['star_rating'] : '';
|
||
|
||
// 获取数据
|
||
$categories = UrlNav_Plugin::getAllCategories();
|
||
$urlsData = UrlNav_Plugin::getAllUrls($currentCategory, $currentPage, $pageSize, $searchKeyword, $currentStatus, $currentHasRss, $currentStarRating);
|
||
|
||
// 获取每个分类的统计信息
|
||
$categoryStats = [];
|
||
foreach ($categories as $category) {
|
||
$categoryStats[$category['id']] = UrlNav_Plugin::getCategoryStats($category['id']);
|
||
}
|
||
|
||
// 获取状态统计(包含RSS统计)
|
||
$statusStats = UrlNav_Plugin::getStatusStats();
|
||
?>
|
||
|
||
<style>
|
||
|
||
/* 星级筛选样式 */
|
||
.star-filter {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
flex-shrink: 0;
|
||
}
|
||
.star-filter select {
|
||
text-align: center;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
background: white;
|
||
height: 32px!important;
|
||
width: 80px;
|
||
}
|
||
|
||
/* 星级显示样式 */
|
||
.star-cell {
|
||
text-align: center;
|
||
width: 50px;
|
||
min-width: 50px;
|
||
}
|
||
.star-rating {
|
||
display: inline-block;
|
||
padding: 2px 6px;
|
||
border-radius: 3px;
|
||
font-size: 12px;
|
||
cursor: default;
|
||
}
|
||
.star-rating.has-star {
|
||
color: #ffc107;
|
||
font-weight: bold;
|
||
}
|
||
.star-rating.no-star {
|
||
color: #999;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* 响应式调整 */
|
||
@media (max-width: 768px) {
|
||
.star-filter select {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
}
|
||
.star-cell {
|
||
width: 40px;
|
||
min-width: 40px;
|
||
}
|
||
}
|
||
|
||
.search-container input::placeholder{color:#000!important;text-align:center;}
|
||
#category-list{text-align:center!important;}
|
||
#url-list td{text-align:center!important;}
|
||
th{text-align:center!important;}
|
||
.check-time-cell{color:#467b96!important;}
|
||
/* 进度窗口样式 */
|
||
#batch-progress-container {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
}
|
||
|
||
#batch-progress-container h4 {
|
||
margin-bottom: 15px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
#progress-status {
|
||
font-size: 14px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
#progress-stats {
|
||
font-size: 13px;
|
||
padding: 8px;
|
||
background: #f8f9fa;
|
||
border-radius: 4px;
|
||
border: 1px solid #dee2e6;
|
||
}
|
||
|
||
#progress-bar {
|
||
transition: width 0.5s ease-in-out;
|
||
}
|
||
#stat-rss-no{color:#fff!important;}
|
||
#stat-unchecked{color:#28a745!important;}
|
||
.dropdown-toggle{background:#467b96!important;color:#fff!important;border-width:0px!important;}
|
||
|
||
/* 原有CSS样式保持不变 */
|
||
.btn-s{height:32px!important;}
|
||
.rss-filter {display: flex;align-items: center;gap: 5px;flex-shrink: 0;}
|
||
.rss-filter select {text-align:center;border: 1px solid #ddd;border-radius: 4px;font-size: 13px;background: white;width: 80px;height: 32px!important;}
|
||
@media (max-width: 768px) {.rss-filter select {width: 100%;max-width: 100%;}}
|
||
.urlnav-management {padding: 20px;}
|
||
#export-opml-btn{height:18px!important;}
|
||
.cz{height:14px!important;}
|
||
.search-container .btn {height:14px!important;}
|
||
.typecho-list-operate {display: flex;justify-content: space-between;align-items: center;flex-wrap: nowrap;gap: 10px;margin-bottom: 20px;overflow: visible !important;}
|
||
.operate-form-left {flex-shrink: 0;overflow: visible !important;}
|
||
.typecho-table-wrap{padding:0px 0!important;}
|
||
.operate {display: flex;align-items: center;gap: 10px;position: relative;overflow: visible !important;}
|
||
.btn-drop {position: relative;overflow: visible !important;}
|
||
.dropdown-menu {position: absolute;top: 100%;text-align: center;left: 0;z-index: 1000;min-width: 100px;background: white;border: 1px solid #ddd;border-radius: 4px;box-shadow: 0 2px 8px rgba(0,0,0,0.1);margin-top: 2px;display: none;overflow: visible !important;}
|
||
.dropdown-menu.show {display: block;}
|
||
.typecho-page-title{display:none;}
|
||
.filter-search-container {display: flex;align-items: center;gap: 10px;height: 32px!important;justify-content: center;flex-wrap: wrap;flex: 1;min-width: 0;}
|
||
.category-filter {display: flex;align-items: center;gap: 5px;flex-shrink: 0;}
|
||
.category-filter select {text-align:center;border: 1px solid #ddd;border-radius: 4px;font-size: 13px;background: white;height: 32px!important;width: 100px;}
|
||
.search-container {display: flex;gap: 10px;align-items: center;flex-shrink: 0;}
|
||
.search-input {padding: 6px 12px;border: 1px solid #ddd;border-radius: 4px!important;font-size: 13px;width: 100px;min-width: 100px;}
|
||
.search-button {padding: 6px 15px;background: #467b96;color: white;border: none;border-radius: 4px;cursor: pointer;font-size: 13px;}
|
||
.search-button:hover {background: #3a6a83;}
|
||
.btn-group {display: flex;gap: 10px;flex-shrink: 0;}
|
||
.btn {padding: 6px 16px;border: 1px solid #ddd;border-radius: 4px;background: white;color: #333;cursor: pointer;font-size: 13px;text-decoration: none;display: inline-flex;align-items: center;gap: 5px;white-space: nowrap;}
|
||
.btn:hover {background: #f5f5f5;text-decoration: none;}
|
||
.btn.primary {background: #467b96;color: white;border-color: #467b96;}
|
||
.btn.primary:hover {background: #3a6a83;border-color: #3a6a83;color: white;}
|
||
.btn.danger {background: #dc3545;color: white;border-color: #dc3545;}
|
||
.btn.danger:hover {background: #c82333;border-color: #c82333;color: white;}
|
||
.typecho-list-table th, .typecho-list-table td {vertical-align: middle;padding: 12px 8px;}
|
||
.url-cell {max-width: 300px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
|
||
.url-link {color: #467b96;text-decoration: none;}
|
||
.url-link:hover {text-decoration: underline;}
|
||
.description-cell {max-width: 200px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
|
||
.rss-cell {max-width: 200px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
|
||
.rss-link {color: #28a745;text-decoration: none;font-size: 12px;display: inline-block;padding: 2px 6px;background: #e8f5e9;border-radius: 3px;}
|
||
.rss-link:hover {text-decoration: underline;background: #d4edda;}
|
||
.no-rss {color: #467b96;font-size: 12px;}
|
||
.category-tag {display: inline-block;padding: 2px 6px;background: #e8f4fd;color: #28a745;border-radius: 3px;font-size: 12px;}
|
||
.no-category {color: #999;font-style: italic;}
|
||
.action-cell {white-space: nowrap;}
|
||
.action-link {color: #467b96;text-decoration: none;margin-right: 10px;font-size: 13px;cursor: pointer;}
|
||
.action-link:hover {text-decoration: underline;}
|
||
.action-link.delete {color: #dc3545;}
|
||
.status-cell {text-align: center;width: 60px;min-width: 60px;}
|
||
.status-indicator {display: inline-block;width: 12px;height: 12px;border-radius: 50%;position: relative;cursor: pointer;transition: all 0.3s ease;}
|
||
.status-indicator:hover {transform: scale(1.2);}
|
||
.status-indicator:active {transform: scale(1.1);opacity: 0.8;}
|
||
.status-online {background-color: #28a745;box-shadow: 0 0 8px rgba(40, 167, 69, 0.6);}
|
||
.status-offline {background-color: #dc3545;box-shadow: 0 0 8px rgba(220, 53, 69, 0.4);}
|
||
.status-unknown {background-color: #6c757d;box-shadow: 0 0 8px rgba(108, 117, 125, 0.4);}
|
||
.status-checking {background-color: #ffc107;animation: pulse 1.5s infinite;box-shadow: 0 0 8px rgba(255, 193, 7, 0.6);}
|
||
@keyframes pulse {0% { opacity: 0.6; transform: scale(1); }50% { opacity: 1; transform: scale(1.1); }100% { opacity: 0.6; transform: scale(1); }}
|
||
.status-tooltip {position: relative;}
|
||
.status-tooltip:hover::after {content: attr(title);position: absolute;bottom: 100%;left: 50%;transform: translateX(-50%);background: rgba(0, 0, 0, 0.85);color: white;padding: 6px 10px;border-radius: 4px;font-size: 12px;white-space: nowrap;z-index: 1000;min-width: 200px;text-align: center;box-shadow: 0 2px 8px rgba(0,0,0,0.2);}
|
||
.status-stats {display: flex;gap: 20px;margin-bottom: 15px;padding: 12px 20px;background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);border-radius: 8px;border: 1px solid #dee2e6;flex-wrap: wrap;justify-content: space-around;}
|
||
.stat-item {display: flex;flex-direction: column;align-items: center;min-width: 80px;}
|
||
.stat-value {font-size: 20px;font-weight: bold;margin-bottom: 4px;}
|
||
.stat-label {font-size: 12px;color: #6c757d;font-weight: 500;}
|
||
#check-status-btn .spinner {display: inline-block;width: 14px;height: 14px;border: 2px solid rgba(255,255,255,.3);border-radius: 50%;border-top-color: #fff;animation: spin 1s ease-in-out infinite;margin-right: 5px;vertical-align: middle;}
|
||
@keyframes spin {to { transform: rotate(360deg); }}
|
||
.modal-overlay {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: rgba(0, 0, 0, 0.5);display: none;z-index: 10000;align-items: center;justify-content: center;overflow-y: auto;padding: 20px;}
|
||
.modal-container {background: white;border-radius: 8px;width: 90%;max-width: 500px;max-height: 85vh;overflow: hidden;display: none;animation: modalFadeIn 0.3s ease;margin: auto;}
|
||
@keyframes modalFadeIn {from {opacity: 0;transform: translateY(-20px);}to {opacity: 1;transform: translateY(0);}}
|
||
.modal-header {padding: 20px;border-bottom: 1px solid #eee;display: flex;justify-content: space-between;align-items: center;background: #f8f9fa;position: sticky;top: 0;z-index: 10;}
|
||
.modal-title {margin: 0;font-size: 18px;color: #333;}
|
||
.modal-close {background: none;border: none;font-size: 24px;cursor: pointer;color: #666;line-height: 1;}
|
||
.modal-close:hover {color: #333;}
|
||
.modal-body {padding: 20px;overflow-y: auto;max-height: calc(85vh - 140px);}
|
||
.modal-footer {padding: 15px 20px;border-top: 1px solid #eee;display: flex;justify-content: flex-end;gap: 10px;background: #f8f9fa;position: sticky;bottom: 0;z-index: 10;}
|
||
.form-group {margin-bottom: 20px;}
|
||
.form-label {display: block;margin-bottom: 8px;font-weight: 500;color: #333;font-size: 14px;}
|
||
.form-label.required::after {content: "*";color: #dc3545;margin-left: 4px;}
|
||
.form-input {width: 100%;padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;font-size: 14px;box-sizing: border-box;}
|
||
.form-input:focus {outline: none;border-color: #467b96;box-shadow: 0 0 0 3px rgba(70, 123, 150, 0.1);}
|
||
.form-textarea {width: 100%;padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;font-size: 14px;min-height: 80px;resize: vertical;box-sizing: border-box;}
|
||
.form-select {width: 100%;padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;font-size: 14px;background: white;max-height: 200px;overflow-y: auto;box-sizing: border-box;min-height: 38px;}
|
||
.form-select option {padding: 8px 12px;white-space: normal;word-wrap: break-word;}
|
||
.form-help {color: #666;font-size: 12px;margin-top: 4px;}
|
||
.rss-autofetch-section {margin: 15px 0;padding: 15px;background: #f8f9fa;border-radius: 6px;border-left: 4px solid #28a745;}
|
||
.rss-autofetch-section h4 {margin-top: 0;margin-bottom: 10px;color: #28a745;font-size: 14px;}
|
||
.rss-urls-list {max-height: 200px;overflow-y: auto;border: 1px solid #ddd;border-radius: 4px;background: white;margin-bottom: 10px;}
|
||
.rss-url-item {padding: 8px 12px;border-bottom: 1px solid #eee;cursor: pointer;transition: background-color 0.2s;}
|
||
.rss-url-item:hover {background-color: #e8f5e9;}
|
||
.rss-url-item:last-child {border-bottom: none;}
|
||
.rss-url-item.selected {background-color: #d4edda;border-left: 3px solid #28a745;}
|
||
.rss-url-item .url {font-size: 13px;color: #333;word-break: break-all;}
|
||
.rss-url-item .type {font-size: 11px;color: #666;background: #f1f1f1;padding: 1px 4px;border-radius: 2px;margin-left: 5px;}
|
||
.fetch-rss-btn {background: #28a745;color: white;border: none;padding: 8px 16px;border-radius: 4px;cursor: pointer;font-size: 13px;display: inline-flex;align-items: center;gap: 5px;}
|
||
.fetch-rss-btn:hover {background: #218838;}
|
||
.fetch-rss-btn:disabled {background: #6c757d;cursor: not-allowed;}
|
||
.fetching-spinner {display: inline-block;width: 16px;height: 16px;border: 2px solid rgba(255,255,255,.3);border-radius: 50%;border-top-color: #fff;animation: spin 1s ease-in-out infinite;}
|
||
.alert {padding: 12px 15px;border-radius: 4px;margin-bottom: 15px;display: none;position: fixed;top: 20px;left: 50%;transform: translateX(-50%);z-index: 99999;max-width: 90%;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);}
|
||
.alert.success {background: #d4edda;border: 1px solid #c3e6cb;color: #155724;}
|
||
.alert.error {background: #f8d7da;border: 1px solid #f5c6cb;color: #721c24;}
|
||
.alert.warning {background: #fff3cd;border: 1px solid #ffeaa7;color: #856404;}
|
||
.alert.info {background: #d1ecf1;border: 1px solid #bee5eb;color: #0c5460;}
|
||
.loading-overlay {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: rgba(255, 255, 255, 0.8);display: none;align-items: center;justify-content: center;z-index: 99999;}
|
||
.loading-spinner {width: 40px;height: 40px;border: 3px solid #f3f3f3;border-top: 3px solid #467b96;border-radius: 50%;animation: spin 1s linear infinite;}
|
||
.tab-container {border-bottom: 1px solid #333;margin-bottom: 20px;padding-bottom: 10px;}
|
||
.tab-buttons {display: flex;gap: 10px;justify-content: center;}
|
||
.tab-button {padding: 10px 20px;background: none;border: 1px solid transparent;border-bottom: 2px solid transparent;cursor: pointer;color: #FFF;font-size: 14px;}
|
||
.tab-button:hover {color: #333;}
|
||
.tab-button.active {color: #467b96;border-bottom-color: #467b96;background: #f8f9fa;border-radius:8px;}
|
||
.tab-content {display: none;}
|
||
.tab-content.active {display: block;}
|
||
.category-actions {display: flex;gap: 10px;margin-bottom: 20px;}
|
||
|
||
/* 网址表格列宽 */
|
||
#urls-tab .typecho-list-table {
|
||
width: 100%;
|
||
min-width: 800px;
|
||
table-layout: fixed;
|
||
border-collapse: collapse;
|
||
}
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(1) { width: 3%; }
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(2) { width: 5%; }
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(3) { width: 9%; }
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(4) { width: 20%; }
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(5) { width: 5%; }
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(6) { width: 25%; }
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(7) { width: 5%; }
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(8) { width: 6%; } /* 星级列 */
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(9) { width: 8%; }
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(10) { width: 5%; }
|
||
#urls-tab .typecho-list-table colgroup col:nth-child(11) { width: 8%; }
|
||
|
||
/* 分类表格列宽 */
|
||
#categories-tab .typecho-list-table {
|
||
width: 100%;
|
||
min-width: 600px;
|
||
table-layout: fixed;
|
||
border-collapse: collapse;
|
||
}
|
||
#categories-tab .typecho-list-table colgroup col:nth-child(1) { width: 3%; }
|
||
#categories-tab .typecho-list-table colgroup col:nth-child(2) { width: 5%; }
|
||
#categories-tab .typecho-list-table colgroup col:nth-child(3) { width: 10%; }
|
||
#categories-tab .typecho-list-table colgroup col:nth-child(4) { width: 40%; }
|
||
#categories-tab .typecho-list-table colgroup col:nth-child(5) { width: 10%; }
|
||
#categories-tab .typecho-list-table colgroup col:nth-child(6) { width: 8%; }
|
||
#categories-tab .typecho-list-table colgroup col:nth-child(7) { width: 8%; }
|
||
#categories-tab .typecho-list-table colgroup col:nth-child(8) { width: 7%; }
|
||
|
||
.status-filter {display: flex;align-items: center;gap: 5px;flex-shrink: 0;}
|
||
.status-filter select {text-align:center;border: 1px solid #ddd;border-radius: 4px;font-size: 13px;background: white;height: 32px!important;width: 80px;}
|
||
.tabs {margin-bottom: 20px;}
|
||
.cron-info-section {background: #f8f9fa;padding: 20px;border-radius: 8px;margin-bottom: 20px;}
|
||
.cron-stats {display: grid;grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));gap: 15px;margin: 20px 0;}
|
||
.cron-stat-item {background: white;padding: 15px;border-radius: 6px;text-align: center;border: 1px solid #dee2e6;}
|
||
.cron-stat-value {font-size: 24px;font-weight: bold;margin-bottom: 5px;}
|
||
.cron-stat-label {font-size: 12px;color: #6c757d;}
|
||
.cron-urls {margin: 20px 0;}
|
||
.url-box {display: flex;gap: 10px;margin-bottom: 10px;}
|
||
.url-input {flex: 1;padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;background: white;font-size: 13px;font-family: monospace;}
|
||
.copy-btn {white-space: nowrap;height: 18px !important;padding: 4px 12px !important;font-size: 12px !important;}
|
||
.cron-actions {display: flex;gap: 10px;margin-top: 20px;}
|
||
.logs-container {border-radius: 6px;padding: 10px;background: #f8f9fa;}
|
||
.cron-log-item {padding: 10px;border-bottom: 1px solid #dee2e6;font-size: 12px;}
|
||
.cron-log-item:last-child {border-bottom: none;}
|
||
.cron-log-time {color: #6c757d;font-weight: bold;}
|
||
.cron-log-type {display: inline-block;padding: 2px 6px;border-radius: 3px;font-size: 11px;margin-right: 10px;}
|
||
.cron-log-type.status {background: #cce5ff;color: #004085;}
|
||
.cron-log-type.error {background: #f8d7da;color: #721c24;}
|
||
.cron-log-message {margin-top: 5px;color: #495057;word-break: break-word;}
|
||
.btn.small {padding: 4px 12px !important;font-size: 12px !important;height: auto !important;}
|
||
@media (max-width: 768px) {.status-filter select {width: 100%;max-width: 100%;}}
|
||
@media (max-width: 768px) {
|
||
.typecho-list-operate {flex-direction: column;align-items: stretch;flex-wrap: wrap;}
|
||
.filter-search-container {flex-direction: column;align-items: stretch;}
|
||
.search-input,.category-filter select {width: 100%;max-width: 100%;}
|
||
.btn-group {width: 100%;justify-content: center;}
|
||
.url-cell {max-width: 150px;}
|
||
.description-cell {max-width: 100px;}
|
||
.rss-cell {max-width: 100px;}
|
||
.status-cell {width: 40px;min-width: 40px;}
|
||
.modal-container {width: 95%;margin: 10px;}
|
||
.status-stats {flex-direction: column;gap: 10px;}
|
||
.stat-item {flex-direction: row;justify-content: space-between;width: 100%;}
|
||
}
|
||
/* 新增:统计数字样式 */
|
||
.stat-number {
|
||
display: inline-block;
|
||
min-width: 20px;
|
||
text-align: center;
|
||
font-weight: bold;
|
||
padding: 2px 6px;
|
||
border-radius: 3px;
|
||
font-size: 12px;
|
||
}
|
||
.url-count {
|
||
background-color: #e8f4fd;
|
||
color: #28a745;
|
||
}
|
||
.rss-count {
|
||
background-color: #e8f5e9;
|
||
color: #28a745;
|
||
}
|
||
.rss-yes-count {
|
||
background-color: #e8f5e9;
|
||
color: #28a745;
|
||
}
|
||
.rss-no-count {
|
||
background-color: #f8f9fa;
|
||
color: #6c757d;
|
||
}
|
||
|
||
/* 页码样式优化 */
|
||
.typecho-pager {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 5px;
|
||
margin: 20px 0;
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
|
||
.typecho-pager li {
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
.typecho-pager a,
|
||
.typecho-pager span {
|
||
display: inline-block;
|
||
padding: 5px 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
text-decoration: none;
|
||
color: #467b96;
|
||
font-size: 13px;
|
||
min-width: 30px;
|
||
text-align: center;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.typecho-pager a:hover {
|
||
background-color: #f5f5f5;
|
||
border-color: #467b96;
|
||
color: #333;
|
||
}
|
||
|
||
.typecho-pager .current {
|
||
background-color: #467b96;
|
||
border-color: #467b96;
|
||
color: white;
|
||
cursor: default;
|
||
}
|
||
|
||
.typecho-pager .ellipsis {
|
||
padding: 5px 8px;
|
||
border: none;
|
||
color: #999;
|
||
cursor: default;
|
||
}
|
||
|
||
.typecho-pager .disabled {
|
||
color: #999;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.typecho-pager .disabled:hover {
|
||
background-color: transparent;
|
||
border-color: #ddd;
|
||
}
|
||
|
||
/* 网址管理页面的页码去除边框 */
|
||
#urls-tab .typecho-pager {
|
||
border: none !important;
|
||
}
|
||
|
||
#urls-tab .typecho-pager a,
|
||
#urls-tab .typecho-pager span {
|
||
border: none !important;
|
||
background: transparent !important;
|
||
}
|
||
|
||
#urls-tab .typecho-pager .current {
|
||
background-color: #467b96 !important;
|
||
color: white !important;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
#urls-tab .typecho-pager a:hover {
|
||
background-color: #f5f5f5 !important;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* 分类管理页面操作栏独立样式 */
|
||
#categories-tab .typecho-list-operate {
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
#categories-tab .operate-form-left {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
#categories-tab .btn-group {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
/* 修改原有CSS,只改变位置和z-index,保持其他样式不变 */
|
||
#batch-progress-container {
|
||
position: fixed;
|
||
top: 100px; /* 改为页面顶部 */
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: 99999; /* 增加z-index确保在最前面 */
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
||
min-width: 400px;
|
||
max-width: 500px;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
border: 1px solid #dee2e6;
|
||
}
|
||
|
||
/* 保持你原有的其他样式完全不变 */
|
||
#batch-progress-container h4 {
|
||
margin-bottom: 15px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
#progress-status {
|
||
font-size: 14px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
#progress-stats {
|
||
font-size: 13px;
|
||
padding: 8px;
|
||
background: #f8f9fa;
|
||
border-radius: 4px;
|
||
border: 1px solid #dee2e6;
|
||
}
|
||
|
||
#progress-bar {
|
||
transition: width 0.5s ease-in-out;
|
||
}
|
||
|
||
/* 检查时间列样式 */
|
||
.check-time-cell {
|
||
font-size: 12px;
|
||
color: #666;
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
width: 70px;
|
||
min-width: 70px;
|
||
}
|
||
.no-check-time {
|
||
font-style: italic;
|
||
color: #999;
|
||
}
|
||
|
||
/* 响应式调整 */
|
||
@media (max-width: 768px) {
|
||
#batch-progress-container {
|
||
min-width: 90%;
|
||
max-width: 95%;
|
||
top: 80px;
|
||
padding: 15px;
|
||
}
|
||
.check-time-cell {
|
||
width: 50px;
|
||
min-width: 50px;
|
||
font-size: 11px;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<!-- 消息提示 -->
|
||
<div class="alert" id="message-alert" style="display: none;"></div>
|
||
<!-- 新增:分批检查进度显示(通过JS动态创建) -->
|
||
<div id="batch-progress-container" style="display: none;"></div>
|
||
<!-- 加载动画 -->
|
||
<div class="loading-overlay" id="loading-overlay">
|
||
<div class="loading-spinner"></div>
|
||
</div>
|
||
<div class="main">
|
||
<div class="body container">
|
||
<?php include 'page-title.php'; ?>
|
||
<div class="row typecho-page-main" role="main">
|
||
<div class="col-mb-12 typecho-list">
|
||
<!-- 选项卡 -->
|
||
<div class="tab-container">
|
||
<div class="tab-buttons">
|
||
<button type="button" class="tab-button active" data-tab="urls">网址管理</button>
|
||
<button type="button" class="tab-button" data-tab="categories">分类管理</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 网址管理 -->
|
||
<div class="tab-content active" id="urls-tab">
|
||
<!-- 状态统计 -->
|
||
<div class="status-stats" id="status-stats">
|
||
<div class="stat-item">
|
||
<span class="stat-value" id="stat-total"><?php echo $statusStats['total']; ?></span>
|
||
<span class="stat-label">总数</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-value" style="color:#28a745;" id="stat-online"><?php echo $statusStats['online']; ?></span>
|
||
<span class="stat-label">通连</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-value" style="color:#dc3545;" id="stat-offline"><?php echo $statusStats['offline']; ?></span>
|
||
<span class="stat-label">失连</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-value" style="color:#6c757d;" id="stat-unchecked"><?php echo $statusStats['unchecked']; ?></span>
|
||
<span class="stat-label">未检</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-value" id="stat-rate"><?php echo round($statusStats['online_rate']); ?></span>
|
||
<span class="stat-label">通连率</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-value" style="color:#28a745;" id="stat-rss-yes"><?php echo $statusStats['has_rss']; ?></span>
|
||
<span class="stat-label">有RSS</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-value" style="color:#6c757d;" id="stat-rss-no"><?php echo $statusStats['no_rss']; ?></span>
|
||
<span class="stat-label">无RSS</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="typecho-list-operate clearfix">
|
||
<form method="get" class="operate-form-left">
|
||
<div class="operate" >
|
||
<div class="btn-group btn-drop">
|
||
<button class="btn dropdown-toggle btn-s" type="button"><i
|
||
class="sr-only"><?php _e('操作'); ?></i><?php _e('多选操作'); ?> <i
|
||
class="i-caret-down"></i></button>
|
||
<ul class="dropdown-menu">
|
||
<li>
|
||
<a href="javascript:void(0);" id="batch-delete-urls-btn"><?php _e('删除'); ?></a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<button type="button" class="btn" id="check-status-btn" title="检查网站状态">
|
||
<span class="spinner" style="display: none;"></span>
|
||
<span class="btn-text">检查</span>
|
||
</button>
|
||
<!-- 新增:定时任务状态按钮 -->
|
||
<button type="button" class="btn" id="cron-status-btn">
|
||
<span>统计</span>
|
||
</button>
|
||
</div>
|
||
</form>
|
||
|
||
<!-- 筛选和搜索 -->
|
||
<div class="filter-search-container">
|
||
<!-- 分类筛选 -->
|
||
<div class="category-filter">
|
||
<select id="category-filter">
|
||
<option value="">全部分类</option>
|
||
<?php foreach ($categories as $category): ?>
|
||
<option value="<?php echo $category['id']; ?>" <?php echo $currentCategory == $category['id'] ? 'selected' : ''; ?>>
|
||
<?php echo htmlspecialchars($category['name']); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<!-- 状态筛选 -->
|
||
<div class="status-filter">
|
||
<select id="status-filter">
|
||
<option value="">全部状态</option>
|
||
<option value="online" <?php echo $currentStatus == 'online' ? 'selected' : ''; ?>>通连</option>
|
||
<option value="offline" <?php echo $currentStatus == 'offline' ? 'selected' : ''; ?>>失连</option>
|
||
<option value="unchecked" <?php echo $currentStatus == 'unchecked' ? 'selected' : ''; ?>>未查</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- RSS筛选 -->
|
||
<div class="rss-filter">
|
||
<select id="rss-filter">
|
||
<option value="">全部RSS</option>
|
||
<option value="yes" <?php echo $currentHasRss == 'yes' ? 'selected' : ''; ?>>有RSS</option>
|
||
<option value="no" <?php echo $currentHasRss == 'no' ? 'selected' : ''; ?>>无RSS</option>
|
||
</select>
|
||
</div>
|
||
<!-- 星级筛选 -->
|
||
<div class="star-filter">
|
||
<select id="star-filter">
|
||
<option value="">全部星级</option>
|
||
<option value="starred" <?php echo $currentStarRating == 'starred' ? 'selected' : ''; ?>>有星级</option>
|
||
<option value="0" <?php echo $currentStarRating === '0' ? 'selected' : ''; ?>>无星级</option>
|
||
<option value="1" <?php echo $currentStarRating == '1' ? 'selected' : ''; ?>>★</option>
|
||
<option value="2" <?php echo $currentStarRating == '2' ? 'selected' : ''; ?>>★★</option>
|
||
<option value="3" <?php echo $currentStarRating == '3' ? 'selected' : ''; ?>>★★★</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 搜索框 -->
|
||
<div class="search-container">
|
||
<form method="get" action="" style="display: flex; align-items: center; gap: 10px;">
|
||
<input type="text" name="search" class="search-input"
|
||
placeholder="<?php _e('搜索网址...'); ?>"
|
||
value="<?php echo htmlspecialchars($searchKeyword); ?>">
|
||
<input type="hidden" name="has_rss" value="<?php echo htmlspecialchars($currentHasRss); ?>">
|
||
<input type="hidden" name="star_rating" value="<?php echo htmlspecialchars($currentStarRating); ?>">
|
||
<input type="hidden" name="status" value="<?php echo htmlspecialchars($currentStatus); ?>">
|
||
<input type="hidden" name="category" value="<?php echo htmlspecialchars($currentCategory); ?>">
|
||
<input type="hidden" name="panel" value="UrlNav/Manage.php">
|
||
<?php if ($searchKeyword || $currentCategory || $currentStatus|| $currentHasRss || $currentStarRating): ?>
|
||
<a href="<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>"
|
||
class="btn cz"><?php _e('重置'); ?></a>
|
||
<?php endif; ?>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="btn-group">
|
||
<button type="button" class="btn" id="import-opml-btn" title="导入OPML文件">
|
||
<span>导入</span>
|
||
</button>
|
||
<a href="<?php echo Typecho_Common::url('/action/urlnav?do=exportOpml', $options->index); ?>"
|
||
class="btn" id="export-opml-btn" title="导出为OPML文件" target="_blank">
|
||
<span>导出</span>
|
||
</a>
|
||
<!-- 新增检查状态按钮 -->
|
||
<button type="button" class="btn primary" id="add-url-btn">
|
||
<span>新增网址</span>
|
||
</button>
|
||
</div>
|
||
</div><!-- end .typecho-list-operate -->
|
||
|
||
<?php if ($searchKeyword || $currentCategory || $currentStatus || $currentHasRss || $currentStarRating): ?>
|
||
<div class="alert info">
|
||
<p>
|
||
<?php
|
||
$statusNames = [
|
||
'online' => '通连',
|
||
'offline' => '失连',
|
||
'unchecked' => '未查'
|
||
];
|
||
// 新增星级名称映射
|
||
$starNames = [
|
||
'0' => '无星级',
|
||
'1' => '★',
|
||
'2' => '★★',
|
||
'3' => '★★★',
|
||
'starred' => '有星级'
|
||
];
|
||
$filters = [];
|
||
|
||
if ($searchKeyword) {
|
||
$filters[] = '搜索关键词:<strong>"' . htmlspecialchars($searchKeyword) . '"</strong>';
|
||
}
|
||
|
||
if ($currentCategory) {
|
||
$categoryName = '';
|
||
foreach ($categories as $cat) {
|
||
if ($cat['id'] == $currentCategory) {
|
||
$categoryName = $cat['name'];
|
||
break;
|
||
}
|
||
}
|
||
if ($categoryName) {
|
||
$filters[] = '分类:<strong>"' . htmlspecialchars($categoryName) . '"</strong>';
|
||
}
|
||
}
|
||
|
||
if ($currentStatus && isset($statusNames[$currentStatus])) {
|
||
$filters[] = '状态:<strong>"' . $statusNames[$currentStatus] . '"</strong>';
|
||
}
|
||
|
||
// 新增:星级筛选提示
|
||
if ($currentStarRating && isset($starNames[$currentStarRating])) {
|
||
$filters[] = '星级:<strong>"' . $starNames[$currentStarRating] . '"</strong>';
|
||
}
|
||
|
||
if ($currentHasRss) {
|
||
$filters[] = 'RSS:<strong>"' . ($currentHasRss == 'yes' ? '有' : '无') . '"</strong>';
|
||
}
|
||
echo implode(',', $filters);
|
||
?>
|
||
</p>
|
||
<a href="<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>">
|
||
<?php _e('显示全部'); ?>
|
||
</a>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<form method="post" name="manage_urls" class="operate-form">
|
||
<div class="typecho-table-wrap">
|
||
<table class="typecho-list-table">
|
||
<colgroup>
|
||
<col width="3%" class="kit-hidden-mb"/>
|
||
<col width="5%"/>
|
||
<col width="10%"/>
|
||
<col width="4%"/>
|
||
<col width="6%"/>
|
||
<col width="15%"/>
|
||
<col width="5%"/>
|
||
<col width="3%"/> <!-- 新增:星级列 -->
|
||
<col width="30%"/>
|
||
<col width="5%"/>
|
||
<col width="6%"/>
|
||
</colgroup>
|
||
<thead>
|
||
<tr>
|
||
<th class="kit-hidden-mb">
|
||
<form method="get" class="operate-form-left">
|
||
<label><i class="sr-only"><?php _e('全选'); ?></i><input type="checkbox" class="typecho-table-select-all"/></label>
|
||
</form>
|
||
</th>
|
||
<th><?php _e('ID'); ?></th>
|
||
<th><?php _e('标题'); ?></th>
|
||
<th><?php _e('网址'); ?></th>
|
||
<th><?php _e('分类'); ?></th>
|
||
<th><?php _e('网站描述'); ?></th>
|
||
<th><?php _e('状态'); ?></th>
|
||
<th><?php _e('星级'); ?></th> <!-- 新增:星级表头 -->
|
||
<th><?php _e('检查时间'); ?></th>
|
||
<th><?php _e('RSS'); ?></th>
|
||
<th><?php _e('操作'); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="url-list">
|
||
<?php
|
||
$urls = $urlsData['data'];
|
||
|
||
if (empty($urls)) {
|
||
if ($searchKeyword || $currentCategory) {
|
||
echo '<tr><td colspan="11" style="text-align: center;"><h6 class="typecho-list-table-title">' . _t('没有找到符合条件的网址') . '</h6></td></tr>';
|
||
} else {
|
||
echo '<tr><td colspan="11" style="text-align: center;"><h6 class="typecho-list-table-title">' . _t('暂无网址,点击"新增网址"按钮添加') . '</h6></td></tr>';
|
||
}
|
||
} else {
|
||
foreach ($urls as $url) {
|
||
echo '<tr id="url-' . $url['id'] . '">';
|
||
echo '<td class="kit-hidden-mb"><input type="checkbox" value="' . $url['id'] . '" name="url[]"/></td>';
|
||
echo '<td>' . $url['id'] . '</td>';
|
||
echo '<td>' . htmlspecialchars($url['title']) . '</td>';
|
||
echo '<td class="url-cell"><a href="' . htmlspecialchars($url['url']) . '" target="_blank" class="url-link">' . htmlspecialchars($url['url']) . '</a></td>';
|
||
|
||
|
||
|
||
echo '<td>';
|
||
if ($url['category_name']) {
|
||
echo '<span class="category-tag">' . htmlspecialchars($url['category_name']) . '</span>';
|
||
} else {
|
||
echo '<span class="no-category">' . _t('未分类') . '</span>';
|
||
}
|
||
echo '</td>';
|
||
echo '<td class="description-cell">' . htmlspecialchars($url['description'] ?: '-') . '</td>';
|
||
|
||
// 状态列
|
||
echo '<td class="status-cell">';
|
||
$statusClass = 'status-unknown';
|
||
$statusTitle = '未查';
|
||
|
||
if (!empty($url['last_status_check'])) {
|
||
if ($url['is_online'] == 1) {
|
||
$statusClass = 'status-online';
|
||
$statusTitle = '通连';
|
||
if ($url['last_status_code']) {
|
||
$statusTitle .= ' (HTTP ' . $url['last_status_code'] . ')';
|
||
}
|
||
$statusTitle .= ' - 最后检查: ' . date('Y-m-d', strtotime($url['last_status_check']));
|
||
} else {
|
||
$statusClass = 'status-offline';
|
||
$statusTitle = '失连';
|
||
if ($url['last_status_code']) {
|
||
$statusTitle .= ' (HTTP ' . $url['last_status_code'] . ')';
|
||
}
|
||
$statusTitle .= ' - 最后检查: ' . date('Y-m-d', strtotime($url['last_status_check']));
|
||
}
|
||
} else if (!empty($url['created_at'])) {
|
||
$createdDays = floor((time() - strtotime($url['created_at'])) / (60 * 60 * 24));
|
||
if ($createdDays > 30) {
|
||
$statusTitle .= ' (添加超过' . $createdDays . '天)';
|
||
}
|
||
}
|
||
echo '<div class="status-indicator status-tooltip ' . $statusClass . '"
|
||
title="' . htmlspecialchars($statusTitle) . '"
|
||
data-id="' . $url['id'] . '"
|
||
data-url="' . htmlspecialchars($url['url']) . '">
|
||
</div>';
|
||
echo '</td>';
|
||
|
||
// 新增:星级列
|
||
echo '<td class="star-cell">';
|
||
$starRating = isset($url['star_rating']) ? intval($url['star_rating']) : 0;
|
||
$starText = UrlNav_Plugin::getStarRatingText($starRating);
|
||
$starClass = $starRating > 0 ? 'has-star' : 'no-star';
|
||
echo '<span class="star-rating ' . $starClass . '" title="' . $starText . '" data-rating="' . $starRating . '">' . $starText . '</span>';
|
||
echo '</td>';
|
||
|
||
|
||
// 新增:检查时间列
|
||
echo '<td class="check-time-cell">';
|
||
if (!empty($url['last_status_check'])) {
|
||
// 将UTC时间转换为北京时间
|
||
$beijingTime = date('Y-m-d', strtotime($url['last_status_check']) + (8 * 3600));
|
||
echo $beijingTime;
|
||
} else {
|
||
echo '<span class="no-check-time">未检查</span>';
|
||
}
|
||
echo '</td>';
|
||
|
||
echo '<td class="rss-cell">';
|
||
if (!empty($url['rss_url'])) {
|
||
echo '<a href="' . htmlspecialchars($url['rss_url']) . '" target="_blank" class="rss-link" title="' . htmlspecialchars($url['rss_url']) . '">RSS</a>';
|
||
} else {
|
||
echo '<span class="no-rss">无</span>';
|
||
}
|
||
echo '</td>';
|
||
|
||
// 操作列
|
||
echo '<td class="action-cell">';
|
||
echo '<a href="#" onclick="event.preventDefault(); event.stopPropagation(); window.urlnavEditUrl(' . $url['id'] . '); return false;" class="action-link edit-url" data-id="' . $url['id'] . '">' . _t('编辑') . '</a>';
|
||
echo '<a href="#" onclick="event.preventDefault(); event.stopPropagation(); window.urlnavDeleteUrl(' . $url['id'] . '); return false;" class="action-link delete delete-url" data-id="' . $url['id'] . '">' . _t('删除') . '</a>';
|
||
echo '</td>';
|
||
echo '</tr>';
|
||
}
|
||
}
|
||
?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</form>
|
||
|
||
<?php if ($urlsData['totalPages'] > 1): ?>
|
||
<div class="typecho-list-operate clearfix">
|
||
<ul class="typecho-pager">
|
||
<li>
|
||
<?php if ($currentPage > 1): ?>
|
||
<a href="<?php
|
||
$url = \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php&page=' . ($currentPage - 1), $options->adminUrl);
|
||
if ($currentCategory) $url .= '&category=' . urlencode($currentCategory);
|
||
if ($currentStatus) $url .= '&status=' . urlencode($currentStatus);
|
||
if ($currentHasRss) $url .= '&has_rss=' . urlencode($currentHasRss);
|
||
if ($searchKeyword) $url .= '&search=' . urlencode($searchKeyword);
|
||
echo $url;
|
||
?>">
|
||
<?php _e('上一页'); ?>
|
||
</a>
|
||
<?php else: ?>
|
||
<span class="current disabled"><?php _e('上一页'); ?></span>
|
||
<?php endif; ?>
|
||
</li>
|
||
|
||
<?php
|
||
$totalPages = $urlsData['totalPages'];
|
||
// 只显示开始两页、最后两页和当前页附近的页面
|
||
$showPages = [];
|
||
|
||
// 始终显示第1页
|
||
$showPages[] = 1;
|
||
|
||
// 显示第2页(如果总页数>=2)
|
||
if ($totalPages >= 2) {
|
||
$showPages[] = 2;
|
||
}
|
||
|
||
// 显示当前页前后各1页
|
||
for ($i = max(3, $currentPage - 1); $i <= min($totalPages - 2, $currentPage + 1); $i++) {
|
||
if ($i > 2 && $i < $totalPages - 1) {
|
||
$showPages[] = $i;
|
||
}
|
||
}
|
||
|
||
// 显示倒数第2页
|
||
if ($totalPages > 3 && $totalPages - 1 > $currentPage + 1) {
|
||
$showPages[] = $totalPages - 1;
|
||
}
|
||
|
||
// 显示最后一页
|
||
if ($totalPages > 2) {
|
||
$showPages[] = $totalPages;
|
||
}
|
||
|
||
// 去重并排序
|
||
$showPages = array_unique($showPages);
|
||
sort($showPages);
|
||
|
||
$prevPage = 0;
|
||
foreach ($showPages as $page) {
|
||
// 添加省略号
|
||
if ($page - $prevPage > 1) {
|
||
echo '<li><span class="ellipsis">...</span></li>';
|
||
}
|
||
|
||
if ($page === $currentPage) {
|
||
echo '<li><span class="current">' . $page . '</span></li>';
|
||
} else {
|
||
echo '<li><a href="';
|
||
$url = \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php&page=' . $page, $options->adminUrl);
|
||
if ($currentCategory) $url .= '&category=' . urlencode($currentCategory);
|
||
if ($currentStatus) $url .= '&status=' . urlencode($currentStatus);
|
||
if ($currentHasRss) $url .= '&has_rss=' . urlencode($currentHasRss);
|
||
if ($searchKeyword) $url .= '&search=' . urlencode($searchKeyword);
|
||
if ($currentStarRating) $url .= '&star_rating=' . urlencode($currentStarRating);
|
||
echo $url . '">' . $page . '</a></li>';
|
||
}
|
||
$prevPage = $page;
|
||
}
|
||
?>
|
||
|
||
<li>
|
||
<?php if ($currentPage < $urlsData['totalPages']): ?>
|
||
<a href="<?php
|
||
$url = \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php&page=' . ($currentPage + 1), $options->adminUrl);
|
||
if ($currentCategory) $url .= '&category=' . urlencode($currentCategory);
|
||
if ($currentStatus) $url .= '&status=' . urlencode($currentStatus);
|
||
if ($currentHasRss) $url .= '&has_rss=' . urlencode($currentHasRss);
|
||
if ($searchKeyword) $url .= '&search=' . urlencode($searchKeyword);
|
||
echo $url;
|
||
?>">
|
||
<?php _e('下一页'); ?></a>
|
||
<?php else: ?>
|
||
<span class="current disabled"><?php _e('下一页'); ?></span>
|
||
<?php endif; ?>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div><!-- end #urls-tab -->
|
||
|
||
<!-- 分类管理 -->
|
||
<div class="tab-content" id="categories-tab">
|
||
<div class="typecho-list-operate clearfix">
|
||
<form method="get" class="operate-form-left">
|
||
<div class="operate" style="justify-content:flex-start;">
|
||
<div class="btn-group btn-drop">
|
||
<button class="btn dropdown-toggle btn-s" type="button"><i
|
||
class="sr-only"><?php _e('操作'); ?></i><?php _e('多选操作'); ?> <i
|
||
class="i-caret-down"></i></button>
|
||
<ul class="dropdown-menu">
|
||
<li>
|
||
<a href="javascript:void(0);" id="batch-delete-categories-btn"><?php _e('删除'); ?></a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="btn-group">
|
||
<button type="button" class="btn primary" id="add-category-btn">
|
||
<span>新增分类</span>
|
||
</button>
|
||
</div>
|
||
</div><!-- end .typecho-list-operate -->
|
||
|
||
<form method="post" name="manage_categories" class="operate-form">
|
||
<div class="typecho-table-wrap">
|
||
<table class="typecho-list-table">
|
||
<colgroup>
|
||
<col width="3%" class="kit-hidden-mb"/>
|
||
<col width="5%"/>
|
||
<col width="20%"/>
|
||
<col width="30%"/>
|
||
<col width="10%"/>
|
||
<col width="8%"/>
|
||
<col width="8%"/>
|
||
<col width="10%"/>
|
||
</colgroup>
|
||
<thead>
|
||
<tr>
|
||
<th class="kit-hidden-mb">
|
||
<form method="get" class="operate-form-left">
|
||
<label><i class="sr-only"><?php _e('全选'); ?></i><input type="checkbox" class="typecho-table-select-all"/></label>
|
||
</form>
|
||
</th>
|
||
<th><?php _e('ID'); ?></th>
|
||
<th><?php _e('名称'); ?></th>
|
||
<th><?php _e('描述'); ?></th>
|
||
<th><?php _e('排序'); ?></th>
|
||
<th><?php _e('网址'); ?></th>
|
||
<th><?php _e('RSS'); ?></th>
|
||
<th><?php _e('操作'); ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="category-list">
|
||
<?php
|
||
if (empty($categories)) {
|
||
echo '<tr><td colspan="8" style="text-align: center;"><h6 class="typecho-list-table-title">' . _t('暂无分类,点击"新增分类"按钮添加') . '</h6></td></tr>';
|
||
} else {
|
||
foreach ($categories as $category) {
|
||
$stats = isset($categoryStats[$category['id']]) ? $categoryStats[$category['id']] : ['url_count' => 0, 'rss_count' => 0];
|
||
|
||
echo '<tr id="category-' . $category['id'] . '">';
|
||
echo '<td class="kit-hidden-mb"><input type="checkbox" value="' . $category['id'] . '" name="category[]"/></td>';
|
||
echo '<td>' . $category['id'] . '</td>';
|
||
echo '<td>' . htmlspecialchars($category['name']) . '</td>';
|
||
echo '<td>' . htmlspecialchars($category['description'] ?: '-') . '</td>';
|
||
echo '<td>' . $category['sort_order'] . '</td>';
|
||
echo '<td>';
|
||
echo '<span class="stat-number url-count" title="该分类下的网址数量">' . $stats['url_count'] . '</span>';
|
||
echo '</td>';
|
||
echo '<td>';
|
||
echo '<span class="stat-number rss-count" title="该分类下有RSS地址的网址数量">' . $stats['rss_count'] . '</span>';
|
||
echo '</td>';
|
||
echo '<td class="action-cell">';
|
||
echo '<a href="#" onclick="event.preventDefault(); event.stopPropagation(); window.urlnavEditCategory(' . $category['id'] . '); return false;" class="action-link edit-category" data-id="' . $category['id'] . '">' . _t('编辑') . '</a>';
|
||
echo '<a href="#" onclick="event.preventDefault(); event.stopPropagation(); window.urlnavDeleteCategory(' . $category['id'] . '); return false;" class="action-link delete delete-category" data-id="' . $category['id'] . '">' . _t('删除') . '</a>';
|
||
echo '</td>';
|
||
echo '</tr>';
|
||
}
|
||
}
|
||
?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</form>
|
||
</div><!-- end #categories-tab -->
|
||
</div><!-- end .typecho-list -->
|
||
</div><!-- end .typecho-page-main -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 网址模态框 -->
|
||
<div class="modal-overlay" id="url-modal-overlay">
|
||
<div class="modal-container" id="url-modal-container">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title" id="url-modal-title">新增网址</h3>
|
||
<button type="button" class="modal-close" id="url-modal-close">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="url-form-loading" style="display: none; text-align: center; padding: 40px; color: #666;">正在加载...</div>
|
||
<form id="url-form">
|
||
<div class="form-group">
|
||
<label class="form-label required" for="url-title">网站标题</label>
|
||
<input type="text" id="url-title" name="title" class="form-input" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label required" for="url-url">网站地址</label>
|
||
<div style="display: flex; gap: 10px;">
|
||
<input type="url" id="url-url" name="url" class="form-input"
|
||
placeholder="https://example.com" required style="flex: 1;">
|
||
<button type="button" id="fetch-rss-btn" class="fetch-rss-btn" style="white-space: nowrap;">
|
||
<span class="fetch-rss-text">获取网站信息</span>
|
||
<span class="fetching-spinner" style="display: none;"></span>
|
||
</button>
|
||
</div>
|
||
<div class="form-help">请输入完整的网址,包含http://或https://</div>
|
||
</div>
|
||
|
||
<!-- RSS地址自动获取区域 -->
|
||
<div class="rss-autofetch-section" id="rss-autofetch-section" style="display: none;">
|
||
<h4>检测到的RSS地址:</h4>
|
||
<div class="rss-urls-list" id="rss-urls-list">
|
||
<!-- RSS地址列表将动态加载到这里 -->
|
||
</div>
|
||
<div style="font-size: 12px; color: #666; margin-bottom: 10px;">
|
||
点击上面的RSS地址进行选择,选中的地址将自动填入下面的RSS地址字段
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label" for="url-rss-url">RSS地址</label>
|
||
<input type="url" id="url-rss-url" name="rss_url" class="form-input"
|
||
placeholder="https://example.com/feed">
|
||
<div class="form-help">网站的RSS/Atom订阅地址(可选,可自动获取)</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label" for="url-description">网站描述</label>
|
||
<textarea id="url-description" name="description" class="form-textarea"></textarea>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label" for="url-star-rating">星级评分</label>
|
||
<select id="url-star-rating" name="star_rating" class="form-select">
|
||
<?php
|
||
$starOptions = UrlNav_Plugin::getStarRatingOptions();
|
||
foreach ($starOptions as $value => $label) {
|
||
echo '<option value="' . $value . '">' . htmlspecialchars($label) . '</option>';
|
||
}
|
||
?>
|
||
</select>
|
||
<div class="form-help">0-3颗星,用于标识重要程度</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label" for="url-category">所属分类</label>
|
||
<select id="url-category" name="category_id" class="form-select">
|
||
<option value="">未分类</option>
|
||
<?php foreach ($categories as $category): ?>
|
||
<option value="<?php echo $category['id']; ?>"><?php echo htmlspecialchars($category['name']); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label" for="url-sort">排序</label>
|
||
<input type="number" id="url-sort" name="sort_order" class="form-input" value="0">
|
||
<div class="form-help">数字越大排序越靠前</div>
|
||
</div>
|
||
|
||
<input type="hidden" id="url-id" name="id" value="">
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn" id="url-modal-cancel">取消</button>
|
||
<button type="button" class="btn primary" id="url-modal-save">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分类模态框 -->
|
||
<div class="modal-overlay" id="category-modal-overlay">
|
||
<div class="modal-container" id="category-modal-container">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title" id="category-modal-title">新增分类</h3>
|
||
<button type="button" class="modal-close" id="category-modal-close">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="category-form-loading" style="display: none; text-align: center; padding: 40px; color: #666;">正在加载...</div>
|
||
<form id="category-form">
|
||
<div class="form-group">
|
||
<label class="form-label required" for="category-name">分类名称</label>
|
||
<input type="text" id="category-name" name="name" class="form-input" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label" for="category-description">分类描述</label>
|
||
<textarea id="category-description" name="description" class="form-textarea"></textarea>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label" for="category-sort">排序</label>
|
||
<input type="number" id="category-sort" name="sort_order" class="form-input" value="0">
|
||
<div class="form-help">数字越小排序越靠前</div>
|
||
</div>
|
||
|
||
<input type="hidden" id="category-id" name="id" value="">
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn" id="category-modal-cancel">取消</button>
|
||
<button type="button" class="btn primary" id="category-modal-save">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 导入OPML模态框 -->
|
||
<div class="modal-overlay" id="import-modal-overlay">
|
||
<div class="modal-container" id="import-modal-container">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">导入OPML文件</h3>
|
||
<button type="button" class="modal-close" id="import-modal-close">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="import-form" enctype="multipart/form-data">
|
||
<div class="form-group">
|
||
<p style="margin-bottom: 15px; color: #666;">
|
||
支持标准的OPML格式文件导入,将自动创建分类和RSS订阅。
|
||
</p>
|
||
|
||
<div style="background: #f8f9fa; padding: 15px; border-radius: 6px; margin-bottom: 15px; border: 1px solid #e8ebf0;">
|
||
<h4 style="margin-top: 0; margin-bottom: 10px; color: #2c3e50;">导入说明</h4>
|
||
<ul style="margin: 0; padding-left: 20px; color: #666; font-size: 13px;">
|
||
<li>支持标准的OPML 2.0格式</li>
|
||
<li>如果分类不存在会自动创建</li>
|
||
<li>自动跳过重复的网址和RSS地址</li>
|
||
<li>建议文件大小不超过2MB</li>
|
||
<li>导入过程可能需要几秒钟时间</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label required" for="opml-file">选择OPML文件</label>
|
||
<input type="file" id="opml-file" name="opml_file" class="form-input"
|
||
accept=".xml,.opml,.opml.xml" required>
|
||
<div class="form-help">支持 .xml, .opml, .opml.xml 格式文件</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label">导入选项</label>
|
||
<div style="display: flex; flex-direction: column; gap: 10px;">
|
||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||
<input type="checkbox" name="skip_duplicates" checked>
|
||
<span style="font-size: 14px;color:#000;">跳过重复的网址</span>
|
||
</label>
|
||
<label style="display: flex; align-items: center;gap: 8px; cursor: pointer;">
|
||
<input type="checkbox" name="auto_create_categories" checked>
|
||
<span style="font-size: 14px;color:#000;">自动创建不存在的分类</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="import-preview" style="display: none; margin-top: 15px;">
|
||
<h4 style="margin-top: 0; margin-bottom: 10px; color: #2c3e50;">文件预览</h4>
|
||
<div style="background: #f8f9fa; padding: 15px;border-radius: 6px; max-height: 200px; overflow-y: auto; font-family: monospace; font-size: 12px; color: #666; border: 1px solid #e8ebf0;">
|
||
<pre id="file-preview-content"></pre>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn" id="import-modal-cancel">取消</button>
|
||
<button type="button" class="btn primary" id="import-modal-start" disabled>开始导入</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 导入结果模态框 -->
|
||
<div class="modal-overlay" id="import-result-overlay">
|
||
<div class="modal-container" id="import-result-container">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">导入结果</h3>
|
||
<button type="button" class="modal-close" id="import-result-close">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="import-result-content">
|
||
<!-- 导入结果将动态显示在这里 -->
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn" id="import-result-close-btn">关闭</button>
|
||
<button type="button" class="btn primary" id="import-result-refresh">刷新页面</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 状态检查定时任务状态监控 -->
|
||
<div class="modal-overlay" id="cron-status-overlay">
|
||
<div class="modal-container" id="cron-status-container" style="max-width: 800px;">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">定时任务</h3>
|
||
<button type="button" class="modal-close" id="cron-status-close">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="cron-info-section">
|
||
<!--<h4 style="color:#000;">定时任务</h4>-->
|
||
<div class="cron-stats" id="status-cron-stats">
|
||
<div style="text-align: center; padding: 20px; color: #666;">加载中...</div>
|
||
</div>
|
||
|
||
<div class="cron-urls">
|
||
<h5 style="color:#000;">定时任务URL</h5>
|
||
<div class="url-box">
|
||
<input type="text" readonly value="<?php
|
||
echo $options->siteUrl . 'urlnav-status-cron?secret=' . $pluginOptions->statusCronSecret;
|
||
?>" id="status-cron-url" class="url-input">
|
||
<button class="btn small copy-btn" data-clipboard-target="#status-cron-url">复制</button>
|
||
</div>
|
||
<div class="form-help">将此URL添加到宝塔计划任务中,用于自动检查网站状态</div>
|
||
</div>
|
||
|
||
<div class="cron-actions">
|
||
<button class="btn" id="refresh-status-cron-stats">刷新统计</button>
|
||
<button class="btn" id="run-status-cron-btn">立即运行</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="cron-logs-section" style="margin-top: 20px;margin-bottom:30px;">
|
||
<!--<h4 style="color:#000;">近期日志</h4>-->
|
||
<div class="logs-container" id="status-cron-logs">
|
||
<div style="text-align: center; padding: 20px; color: #666;">加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn" id="cron-status-close-btn">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
// 设置插件配置
|
||
window.UrlNavConfig = {
|
||
actionUrl: '<?php echo Typecho_Common::url('/action/urlnav', $options->index); ?>',
|
||
// 状态检查相关
|
||
statusCheckInterval: <?php echo isset($pluginOptions->statusCheckInterval) ? intval($pluginOptions->statusCheckInterval) : 30; ?>,
|
||
batchCheckSize: 30 // 每次批量检查数量
|
||
};
|
||
|
||
// ========== 全局函数定义 ==========
|
||
|
||
// 编辑网址 - 优化加载体验
|
||
window.urlnavEditUrl = function(id) {
|
||
if (!id) {
|
||
console.error('网址ID不能为空');
|
||
return false;
|
||
}
|
||
|
||
console.log('编辑网址,ID:', id);
|
||
|
||
// 先显示模态框
|
||
document.getElementById('url-modal-title').textContent = '编辑网址';
|
||
document.getElementById('url-modal-overlay').style.display = 'flex';
|
||
document.getElementById('url-modal-container').style.display = 'block';
|
||
|
||
// 显示模态框中的加载状态
|
||
$('#url-form').hide();
|
||
$('#url-form-loading').show();
|
||
|
||
// 发送请求获取网址信息
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'GET',
|
||
data: {do: 'getUrl', id: id},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
// 移除加载状态
|
||
$('#url-form-loading').hide();
|
||
$('#url-form').show();
|
||
|
||
if (response.success) {
|
||
// 填充表单
|
||
document.getElementById('url-id').value = response.data.id || '';
|
||
document.getElementById('url-title').value = response.data.title || '';
|
||
document.getElementById('url-url').value = response.data.url || '';
|
||
document.getElementById('url-rss-url').value = response.data.rss_url || '';
|
||
document.getElementById('url-description').value = response.data.description || '';
|
||
document.getElementById('url-category').value = response.data.category_id || '';
|
||
document.getElementById('url-sort').value = response.data.sort_order || 0;
|
||
document.getElementById('url-star-rating').value = response.data.star_rating || '0';
|
||
// 编辑模式下显示RSS自动获取区域
|
||
document.getElementById('rss-autofetch-section').style.display = 'block';
|
||
document.getElementById('rss-urls-list').innerHTML = '<div class="rss-url-item" style="text-align: center; padding: 20px; color: #666;">点击"获取网站信息"按钮获取RSS地址</div>';
|
||
|
||
console.log('网址数据加载完成');
|
||
|
||
// 聚焦到标题输入框
|
||
setTimeout(function() {
|
||
$('#url-title').focus();
|
||
}, 100);
|
||
} else {
|
||
alert(response.message || '获取网址信息失败');
|
||
hideUrlModal();
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
// 移除加载状态
|
||
$('#url-form-loading').hide();
|
||
$('#url-form').show();
|
||
alert('获取网址信息失败: ' + error);
|
||
hideUrlModal();
|
||
}
|
||
});
|
||
|
||
return false;
|
||
};
|
||
|
||
// 删除网址
|
||
window.urlnavDeleteUrl = function(id) {
|
||
if (!id) {
|
||
console.error('网址ID不能为空');
|
||
return false;
|
||
}
|
||
|
||
if (!confirm('你确认要删除这个网址吗?')) {
|
||
return false;
|
||
}
|
||
|
||
console.log('删除网址,ID:', id);
|
||
|
||
// 显示加载动画
|
||
var loadingOverlay = document.getElementById('loading-overlay');
|
||
if (loadingOverlay) loadingOverlay.style.display = 'flex';
|
||
|
||
// 发送删除请求
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'POST',
|
||
data: {do: 'deleteUrl', id: id},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
// 隐藏加载动画
|
||
if (loadingOverlay) loadingOverlay.style.display = 'none';
|
||
|
||
if (response.success) {
|
||
alert(response.message || '删除成功');
|
||
window.location.reload();
|
||
} else {
|
||
alert(response.message || '删除失败');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
// 隐藏加载动画
|
||
if (loadingOverlay) loadingOverlay.style.display = 'none';
|
||
alert('删除失败: ' + error);
|
||
}
|
||
});
|
||
|
||
return false;
|
||
};
|
||
|
||
// 编辑分类
|
||
window.urlnavEditCategory = function(id) {
|
||
if (!id) {
|
||
console.error('分类ID不能为空');
|
||
return false;
|
||
}
|
||
|
||
console.log('编辑分类,ID:', id);
|
||
|
||
// 先显示模态框
|
||
document.getElementById('category-modal-title').textContent = '编辑分类';
|
||
document.getElementById('category-modal-overlay').style.display = 'flex';
|
||
document.getElementById('category-modal-container').style.display = 'block';
|
||
|
||
// 显示模态框中的加载状态
|
||
$('#category-form').hide();
|
||
$('#category-form-loading').show();
|
||
|
||
// 发送请求获取分类信息
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'GET',
|
||
data: {do: 'getCategory', id: id},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
// 移除加载状态
|
||
$('#category-form-loading').hide();
|
||
$('#category-form').show();
|
||
|
||
if (response.success) {
|
||
// 填充表单
|
||
document.getElementById('category-id').value = response.data.id || '';
|
||
document.getElementById('category-name').value = response.data.name || '';
|
||
document.getElementById('category-description').value = response.data.description || '';
|
||
document.getElementById('category-sort').value = response.data.sort_order || 0;
|
||
|
||
console.log('分类数据加载完成');
|
||
|
||
// 聚焦到名称输入框
|
||
setTimeout(function() {
|
||
$('#category-name').focus();
|
||
}, 100);
|
||
} else {
|
||
alert(response.message || '获取分类信息失败');
|
||
hideCategoryModal();
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
// 移除加载状态
|
||
$('#category-form-loading').hide();
|
||
$('#category-form').show();
|
||
alert('获取分类信息失败: ' + error);
|
||
hideCategoryModal();
|
||
}
|
||
});
|
||
|
||
return false;
|
||
};
|
||
|
||
// 删除分类
|
||
window.urlnavDeleteCategory = function(id) {
|
||
if (!id) {
|
||
console.error('分类ID不能为空');
|
||
return false;
|
||
}
|
||
|
||
if (!confirm('你确认要删除这个分类吗?')) {
|
||
return false;
|
||
}
|
||
|
||
console.log('删除分类,ID:', id);
|
||
|
||
// 显示加载动画
|
||
var loadingOverlay = document.getElementById('loading-overlay');
|
||
if (loadingOverlay) loadingOverlay.style.display = 'flex';
|
||
|
||
// 发送删除请求
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'POST',
|
||
data: {do: 'deleteCategory', id: id},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
// 隐藏加载动画
|
||
if (loadingOverlay) loadingOverlay.style.display = 'none';
|
||
|
||
if (response.success) {
|
||
alert(response.message || '删除成功');
|
||
window.location.reload();
|
||
} else {
|
||
alert(response.message || '删除失败');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
// 隐藏加载动画
|
||
if (loadingOverlay) loadingOverlay.style.display = 'none';
|
||
alert('删除失败: ' + error);
|
||
}
|
||
});
|
||
|
||
return false;
|
||
};
|
||
|
||
// 选择RSS地址(全局函数,供HTML调用)
|
||
function selectRssUrl(element) {
|
||
// 移除之前的选择
|
||
var items = document.querySelectorAll('.rss-url-item');
|
||
items.forEach(function(item) {
|
||
item.classList.remove('selected');
|
||
});
|
||
|
||
// 添加当前选择
|
||
element.classList.add('selected');
|
||
|
||
// 获取RSS地址
|
||
var rssUrl = element.getAttribute('data-url');
|
||
document.getElementById('url-rss-url').value = rssUrl;
|
||
|
||
// 显示成功消息
|
||
var alert = document.getElementById('message-alert');
|
||
alert.className = 'alert success';
|
||
alert.innerHTML = '已选择RSS地址:' + rssUrl;
|
||
alert.style.display = 'block';
|
||
|
||
setTimeout(function() {
|
||
alert.style.display = 'none';
|
||
}, 2000);
|
||
}
|
||
|
||
// ========== 其他功能 ==========
|
||
|
||
// 使用立即执行函数确保代码安全执行
|
||
(function($) {
|
||
'use strict';
|
||
|
||
// 全局变量
|
||
var currentTab = 'urls';
|
||
var isEditMode = false;
|
||
var isFetchingRss = false;
|
||
var selectedRssUrl = null;
|
||
var autoCheckTimer = null;
|
||
|
||
// 初始化函数
|
||
function initUrlNav() {
|
||
console.log('UrlNav插件管理页面初始化');
|
||
|
||
// 记录页面加载时间
|
||
window.pageLoadStartTime = Date.now();
|
||
|
||
// 修复全选复选框功能 - 确保选中状态同步
|
||
function syncCheckAllState() {
|
||
var currentTab = $('.tab-content.active').attr('id');
|
||
var allCheckboxes, allChecked;
|
||
|
||
if (currentTab === 'urls-tab') {
|
||
allCheckboxes = $('#url-list input[name="url[]"]');
|
||
} else if (currentTab === 'categories-tab') {
|
||
allCheckboxes = $('#category-list input[name="category[]"]');
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
// 如果没有复选框,全选框设为未选中
|
||
if (allCheckboxes.length === 0) {
|
||
$('.typecho-table-select-all').prop('checked', false);
|
||
return;
|
||
}
|
||
|
||
// 检查是否所有复选框都被选中
|
||
allChecked = allCheckboxes.length === allCheckboxes.filter(':checked').length;
|
||
|
||
// 设置全选复选框状态
|
||
$('.typecho-table-select-all').prop('checked', allChecked);
|
||
}
|
||
|
||
// 点击全选复选框
|
||
$('.typecho-table-select-all').off('change').on('change', function() {
|
||
var isChecked = $(this).is(':checked');
|
||
var currentTab = $('.tab-content.active').attr('id');
|
||
|
||
console.log('全选复选框变化: ' + isChecked + ', 当前标签页: ' + currentTab);
|
||
|
||
if (currentTab === 'urls-tab') {
|
||
$('#url-list input[name="url[]"]').prop('checked', isChecked);
|
||
} else if (currentTab === 'categories-tab') {
|
||
$('#category-list input[name="category[]"]').prop('checked', isChecked);
|
||
}
|
||
});
|
||
|
||
// 点击单个复选框时更新全选状态
|
||
$(document).off('change').on('change', 'input[name="url[]"], input[name="category[]"]', function() {
|
||
console.log('单个复选框变化');
|
||
syncCheckAllState();
|
||
});
|
||
|
||
// 切换标签页时更新
|
||
$('.tab-button').off('click').on('click', function() {
|
||
setTimeout(function() {
|
||
console.log('切换标签页,更新全选状态');
|
||
syncCheckAllState();
|
||
}, 100);
|
||
});
|
||
|
||
// 初始同步状态
|
||
setTimeout(function() {
|
||
console.log('初始同步全选状态');
|
||
syncCheckAllState();
|
||
}, 500);
|
||
|
||
// 选项卡切换
|
||
$('.tab-button').off('click').on('click', function() {
|
||
var tab = $(this).data('tab');
|
||
if (tab) {
|
||
switchTab(tab);
|
||
}
|
||
});
|
||
|
||
// 修复问题1:下拉菜单显示
|
||
$('.dropdown-toggle').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
$(this).next('.dropdown-menu').toggleClass('show');
|
||
});
|
||
|
||
// 点击其他地方关闭下拉菜单
|
||
$(document).off('click').on('click', function(e) {
|
||
if (!$(e.target).closest('.btn-drop').length) {
|
||
$('.dropdown-menu').removeClass('show');
|
||
}
|
||
});
|
||
|
||
// 新增网址按钮 - 确保只绑定一次
|
||
$('#add-url-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
console.log('新增网址按钮点击');
|
||
showUrlModal();
|
||
});
|
||
|
||
// 新增分类按钮 - 确保只绑定一次
|
||
$('#add-category-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
console.log('新增分类按钮点击');
|
||
showCategoryModal();
|
||
});
|
||
|
||
// 检查状态按钮 - 新增
|
||
$('#check-status-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
checkWebsiteStatus();
|
||
});
|
||
|
||
// 定时任务状态按钮
|
||
$('#cron-status-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
showCronStatus();
|
||
});
|
||
|
||
// 网址模态框关闭
|
||
$('#url-modal-close, #url-modal-cancel').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
hideUrlModal();
|
||
});
|
||
|
||
// 分类模态框关闭
|
||
$('#category-modal-close, #category-modal-cancel').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
hideCategoryModal();
|
||
});
|
||
|
||
// 点击模态框背景关闭
|
||
$('.modal-overlay').off('click').on('click', function(e) {
|
||
if (e.target === this) {
|
||
if ($(this).attr('id') === 'url-modal-overlay') {
|
||
hideUrlModal();
|
||
} else if ($(this).attr('id') === 'category-modal-overlay') {
|
||
hideCategoryModal();
|
||
} else if ($(this).attr('id') === 'cron-status-overlay') {
|
||
hideCronStatus();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 保存按钮 - 确保只绑定一次
|
||
$('#url-modal-save').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
console.log('网址保存按钮点击');
|
||
saveUrl();
|
||
});
|
||
|
||
$('#category-modal-save').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
console.log('分类保存按钮点击');
|
||
saveCategory();
|
||
});
|
||
|
||
// 获取RSS按钮 - 确保只绑定一次
|
||
$('#fetch-rss-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
console.log('获取RSS按钮点击');
|
||
fetchRssFromUrl();
|
||
});
|
||
|
||
// 批量删除 - 确保只绑定一次
|
||
$('#batch-delete-urls-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
console.log('批量删除网址按钮点击');
|
||
$('.dropdown-menu').removeClass('show');
|
||
setTimeout(function() {
|
||
batchDeleteUrls();
|
||
}, 50);
|
||
});
|
||
|
||
$('#batch-delete-categories-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
console.log('批量删除分类按钮点击');
|
||
$('.dropdown-menu').removeClass('show');
|
||
setTimeout(function() {
|
||
batchDeleteCategories();
|
||
}, 50);
|
||
});
|
||
|
||
// 分类筛选
|
||
$('#category-filter').off('change').on('change', function() {
|
||
var categoryId = $(this).val();
|
||
var url = '<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>';
|
||
if (categoryId) {
|
||
url += '&category=' + encodeURIComponent(categoryId);
|
||
}
|
||
window.location.href = url;
|
||
});
|
||
|
||
// 状态筛选
|
||
$('#status-filter').off('change').on('change', function() {
|
||
var status = $(this).val();
|
||
var url = '<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>';
|
||
var category = $('#category-filter').val();
|
||
|
||
if (status) {
|
||
url += '&status=' + encodeURIComponent(status);
|
||
}
|
||
if (category) {
|
||
url += '&category=' + encodeURIComponent(category);
|
||
}
|
||
|
||
window.location.href = url;
|
||
});
|
||
// 星级筛选
|
||
$('#star-filter').off('change').on('change', function() {
|
||
var starRating = $(this).val();
|
||
var url = '<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>';
|
||
var category = $('#category-filter').val();
|
||
|
||
if (starRating) {
|
||
url += '&star_rating=' + encodeURIComponent(starRating);
|
||
}
|
||
if (category) {
|
||
url += '&category=' + encodeURIComponent(category);
|
||
}
|
||
|
||
window.location.href = url;
|
||
});
|
||
// RSS筛选
|
||
$('#rss-filter').off('change').on('change', function() {
|
||
var hasRss = $(this).val();
|
||
var url = '<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>';
|
||
var category = $('#category-filter').val();
|
||
|
||
if (hasRss) {
|
||
url += '&has_rss=' + encodeURIComponent(hasRss);
|
||
}
|
||
if (category) {
|
||
url += '&category=' + encodeURIComponent(category);
|
||
}
|
||
|
||
window.location.href = url;
|
||
});
|
||
|
||
// 导入OPML按钮
|
||
$('#import-opml-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
showImportModal();
|
||
});
|
||
|
||
// 导入模态框关闭
|
||
$('#import-modal-close, #import-modal-cancel').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
hideImportModal();
|
||
});
|
||
|
||
// 导入结果模态框关闭
|
||
$('#import-result-close, #import-result-close-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
hideImportResultModal();
|
||
});
|
||
|
||
// 点击模态框背景关闭
|
||
$('#import-modal-overlay, #import-result-overlay').off('click').on('click', function(e) {
|
||
if (e.target === this) {
|
||
if ($(this).attr('id') === 'import-modal-overlay') {
|
||
hideImportModal();
|
||
} else {
|
||
hideImportResultModal();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 开始导入按钮
|
||
$('#import-modal-start').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
startImport();
|
||
});
|
||
|
||
// 刷新页面按钮
|
||
$('#import-result-refresh').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
window.location.reload();
|
||
});
|
||
|
||
// 文件选择预览
|
||
$('#opml-file').off('change').on('change', function(e) {
|
||
var file = this.files[0];
|
||
if (file) {
|
||
previewFile(file);
|
||
}
|
||
});
|
||
|
||
// 定时任务状态模态框关闭
|
||
$('#cron-status-close, #cron-status-close-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
hideCronStatus();
|
||
});
|
||
|
||
// 刷新状态检查定时任务统计
|
||
$('#refresh-status-cron-stats').off('click').on('click', function() {
|
||
loadStatusCronStats();
|
||
});
|
||
|
||
// 立即运行定时任务按钮
|
||
$('#run-status-cron-btn').off('click').on('click', function(e) {
|
||
e.preventDefault();
|
||
runStatusCronNow();
|
||
});
|
||
|
||
// 阻止所有#链接的默认行为
|
||
$(document).off('click', 'a[href="#"]').on('click', 'a[href="#"]', function(e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
});
|
||
|
||
// 页面加载时不自动检查状态,改为手动或定时任务检查
|
||
// 只加载状态统计
|
||
loadStatusStats();
|
||
|
||
// 清空之前的定时器
|
||
if (autoCheckTimer) {
|
||
clearTimeout(autoCheckTimer);
|
||
}
|
||
|
||
// 修复:状态圆圈点击事件绑定 - 简化版本
|
||
$(document).on('click', '.status-indicator', function(e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
var $this = $(this);
|
||
var urlId = $this.data('id');
|
||
|
||
if (!urlId) {
|
||
console.error('缺少网址ID数据');
|
||
return;
|
||
}
|
||
|
||
console.log('点击检查单个网址,ID:', urlId);
|
||
|
||
// 使用统一的单网址检查函数
|
||
checkSingleWebsiteStatus(urlId, $this);
|
||
});
|
||
}
|
||
|
||
// 修复检查单个网址的函数
|
||
function checkSingleWebsiteStatus(urlId, $indicator) {
|
||
if (!$indicator) {
|
||
$indicator = $('.status-indicator[data-id="' + urlId + '"]');
|
||
}
|
||
|
||
// 保存原始状态
|
||
var originalClass = $indicator.attr('class');
|
||
|
||
// 设置为检查中状态
|
||
$indicator.removeClass('status-online status-offline status-unknown')
|
||
.addClass('status-checking');
|
||
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'POST',
|
||
data: {
|
||
do: 'checkSingleStatus',
|
||
id: urlId
|
||
},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
if (response.success && response.data) {
|
||
// 移除检查中状态
|
||
$indicator.removeClass('status-checking');
|
||
|
||
if (response.data.success) {
|
||
// 设置在线状态
|
||
$indicator.addClass('status-online').removeClass('status-offline status-unknown');
|
||
var title = '通连';
|
||
if (response.data.status_code && response.data.status_code > 0) {
|
||
title += ' (HTTP ' + response.data.status_code + ')';
|
||
}
|
||
if (response.data.response_time) {
|
||
title += ' - 响应: ' + response.data.response_time + 'ms';
|
||
}
|
||
title += ' - ' + new Date().toLocaleDateString();
|
||
$indicator.attr('title', title);
|
||
showMessage('网址检查完成:连接正常', 'success');
|
||
} else {
|
||
// 设置离线状态
|
||
$indicator.addClass('status-offline').removeClass('status-online status-unknown');
|
||
var title = '失连: ' + (response.data.message || '访问失败');
|
||
if (response.data.status_code && response.data.status_code > 0) {
|
||
title += ' (HTTP ' + response.data.status_code + ')';
|
||
}
|
||
title += ' - ' + new Date().toLocaleDateString();
|
||
$indicator.attr('title', title);
|
||
showMessage('网址检查完成:连接失败', 'error');
|
||
}
|
||
|
||
// 刷新状态统计
|
||
loadStatusStats();
|
||
} else {
|
||
// 恢复原始状态
|
||
$indicator.removeClass('status-checking').addClass(originalClass);
|
||
showMessage('检查失败: ' + (response.message || '未知错误'), 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
// 恢复原始状态
|
||
$indicator.removeClass('status-checking').addClass(originalClass);
|
||
showMessage('检查请求失败: ' + error, 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 显示网址模态框
|
||
function showUrlModal() {
|
||
console.log('显示网址模态框');
|
||
|
||
// 重置表单(重要:确保是新增模式)
|
||
resetUrlForm();
|
||
|
||
// 确保标题是"新增网址"
|
||
$('#url-modal-title').text('新增网址');
|
||
|
||
// 清除编辑模式下可能遗留的ID
|
||
$('#url-id').val('');
|
||
|
||
// 显示模态框
|
||
$('#url-modal-overlay').fadeIn(200);
|
||
$('#url-modal-container').fadeIn(200);
|
||
|
||
// 确保RSS自动获取区域可见
|
||
$('#rss-autofetch-section').show();
|
||
|
||
setTimeout(function() {
|
||
$('#url-title').focus();
|
||
}, 300);
|
||
}
|
||
|
||
// 隐藏网址模态框
|
||
function hideUrlModal() {
|
||
$('#url-modal-container').fadeOut(200);
|
||
$('#url-modal-overlay').fadeOut(200);
|
||
}
|
||
|
||
// 重置网址表单
|
||
function resetUrlForm() {
|
||
console.log('重置网址表单');
|
||
|
||
// 重置表单所有字段
|
||
$('#url-form')[0].reset();
|
||
|
||
// 明确设置默认值
|
||
$('#url-id').val('');
|
||
$('#url-title').val('');
|
||
$('#url-url').val('');
|
||
$('#url-rss-url').val('');
|
||
$('#url-description').val('');
|
||
$('#url-category').val(''); // 设置为空,不是'0'
|
||
$('#url-sort').val('0');
|
||
|
||
// 重置RSS区域
|
||
$('#rss-autofetch-section').show();
|
||
$('#rss-urls-list').html('<div class="rss-url-item" style="text-align: center; padding: 20px; color: #666;">填写网址后,点击"获取网站信息"按钮</div>');
|
||
|
||
// 重置状态变量
|
||
selectedRssUrl = null;
|
||
isEditMode = false;
|
||
isFetchingRss = false;
|
||
updateFetchRssButton();
|
||
}
|
||
|
||
// 显示分类模态框
|
||
function showCategoryModal() {
|
||
resetCategoryForm();
|
||
|
||
$('#category-modal-title').text('新增分类');
|
||
$('#category-modal-overlay').fadeIn(200);
|
||
$('#category-modal-container').fadeIn(200);
|
||
|
||
setTimeout(function() {
|
||
$('#category-name').focus();
|
||
}, 300);
|
||
}
|
||
|
||
// 隐藏分类模态框
|
||
function hideCategoryModal() {
|
||
$('#category-modal-container').fadeOut(200);
|
||
$('#category-modal-overlay').fadeOut(200);
|
||
}
|
||
|
||
// 重置分类表单
|
||
function resetCategoryForm() {
|
||
$('#category-form')[0].reset();
|
||
$('#category-id').val('');
|
||
$('#category-name').val('');
|
||
$('#category-description').val('');
|
||
$('#category-sort').val('0');
|
||
isEditMode = false;
|
||
}
|
||
|
||
// 显示定时任务状态
|
||
function showCronStatus() {
|
||
$('#cron-status-overlay').fadeIn(200);
|
||
$('#cron-status-container').fadeIn(200);
|
||
loadStatusCronStats();
|
||
}
|
||
|
||
// 隐藏定时任务状态
|
||
function hideCronStatus() {
|
||
$('#cron-status-container').fadeOut(200);
|
||
$('#cron-status-overlay').fadeOut(200);
|
||
}
|
||
|
||
// 加载状态检查定时任务统计
|
||
function loadStatusCronStats() {
|
||
showLoading();
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'GET',
|
||
data: {do: 'getStatusCronStats'},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
hideLoading();
|
||
if (response.success) {
|
||
updateStatusCronStats(response.data);
|
||
loadStatusCronLogs();
|
||
showMessage('状态检查定时任务统计已刷新', 'success');
|
||
} else {
|
||
$('#status-cron-stats').html('<div class="alert error">加载失败: ' + response.message + '</div>');
|
||
showMessage('刷新失败: ' + response.message, 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
hideLoading();
|
||
$('#status-cron-stats').html('<div class="alert error">加载失败</div>');
|
||
showMessage('刷新失败: ' + error, 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 更新状态检查定时任务统计显示
|
||
function updateStatusCronStats(stats) {
|
||
var html = '';
|
||
html += '<div class="cron-stat-item">';
|
||
html += '<div class="cron-stat-value"style="color:#000;">' + stats.total + '</div>';
|
||
html += '<div class="cron-stat-label" >总执行次数</div>';
|
||
html += '</div>';
|
||
|
||
html += '<div class="cron-stat-item">';
|
||
html += '<div class="cron-stat-value" style="color:#28a745;">' + stats.success + '</div>';
|
||
html += '<div class="cron-stat-label">成功次数</div>';
|
||
html += '</div>';
|
||
|
||
html += '<div class="cron-stat-item">';
|
||
html += '<div class="cron-stat-value" style="color:#dc3545;">' + stats.failed + '</div>';
|
||
html += '<div class="cron-stat-label">失败次数</div>';
|
||
html += '</div>';
|
||
|
||
html += '<div class="cron-stat-item">';
|
||
html += '<div class="cron-stat-value" style="color:#000;">' + stats.success_rate + '%</div>';
|
||
html += '<div class="cron-stat-label">成功率</div>';
|
||
html += '</div>';
|
||
|
||
$('#status-cron-stats').html(html);
|
||
}
|
||
|
||
// 加载状态检查定时任务日志(修复版)
|
||
function loadStatusCronLogs() {
|
||
// 显示加载状态
|
||
$('#status-cron-logs').html('<div style="text-align: center; padding: 20px; color: #6c757d;">加载中...</div>');
|
||
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'POST', // 改为POST
|
||
data: {do: 'getCronLogs', type: 'status', limit: 10},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
if (response.success && response.data) {
|
||
// 调用更新函数
|
||
updateStatusCronLogs(response.data);
|
||
} else {
|
||
$('#status-cron-logs').html('<div class="cron-log-item">加载失败: ' + (response.message || '未知错误') + '</div>');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
console.error('加载状态检查日志失败:', error);
|
||
$('#status-cron-logs').html('<div class="cron-log-item">加载失败: ' + error + '</div>');
|
||
}
|
||
});
|
||
}
|
||
|
||
// HTML转义函数
|
||
function escapeHtml(text) {
|
||
if (text == null || text === '') return '';
|
||
return String(text)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
// 优化版本:移除跳转功能,去掉高度限制和滚动条,显示所有内容
|
||
function updateStatusCronLogs(logs) {
|
||
try {
|
||
console.log('开始更新状态检查日志,日志数量:', logs.length);
|
||
|
||
var html = '';
|
||
if (logs.length === 0) {
|
||
html = '<div class="cron-log-item">暂无执行日志</div>';
|
||
} else {
|
||
logs.forEach(function(log, index) {
|
||
console.log('处理第', index, '条日志:', log);
|
||
|
||
var result = null;
|
||
try {
|
||
result = JSON.parse(log.result);
|
||
console.log('日志解析成功:', result);
|
||
} catch (e) {
|
||
console.error('解析JSON失败:', e, '原始内容:', log.result);
|
||
result = {message: log.result};
|
||
}
|
||
|
||
// 转换时间为北京时间
|
||
var beijingTime = convertToBeijingTime(log.executed_time);
|
||
var beijingTimeStr = formatDateTime(beijingTime);
|
||
var localTimeStr = formatDateTime(new Date(log.executed_time));
|
||
|
||
html += '<div class="cron-log-item">';
|
||
html += '<div>';
|
||
html += '<span class="cron-log-time" title="服务器时间: ' + localTimeStr + '">' + beijingTimeStr + '</span>';
|
||
html += '</div>';
|
||
|
||
// 安全显示日志消息
|
||
var logMessage = log.error_message || (result && result.message) || log.result || '执行成功';
|
||
html += '<div class="cron-log-message">' + escapeHtml(String(logMessage)) + '</div>';
|
||
|
||
// 🔴 只显示有具体失败网址的信息,不显示纯统计信息
|
||
if (result) {
|
||
// 尝试从日志结果中提取失败网址
|
||
var failedUrls = result.failedUrls || result.failed_urls || [];
|
||
|
||
console.log('第' + index + '条日志失败网址数量:', failedUrls.length);
|
||
|
||
// 只有有具体的失败网址时才显示
|
||
if (failedUrls.length > 0) {
|
||
console.log('显示失败网址,数量:', failedUrls.length);
|
||
|
||
html += '<div style="margin-top: 10px; padding: 10px; background: #fff5f5; border-radius: 6px; border-left: 4px solid #dc3545;">';
|
||
html += '<div style="font-size: 12px; color: #721c24; margin-bottom: 8px; font-weight: 500;">有问题网站 (' + failedUrls.length + '个):</div>';
|
||
html += '<div style="padding-right: 5px;">'; // 移除高度限制和overflow
|
||
|
||
// 显示所有失败的网址
|
||
failedUrls.forEach(function(site, i) {
|
||
var siteName = site.title || site.url || '未知网站';
|
||
// 截断过长的网站名称
|
||
if (siteName.length > 40) {
|
||
siteName = siteName.substring(0, 40) + '...';
|
||
}
|
||
|
||
// 错误信息处理
|
||
var errorMsg = site.error || '未知错误';
|
||
if (errorMsg.length > 60) {
|
||
errorMsg = errorMsg.substring(0, 60) + '...';
|
||
}
|
||
|
||
// 转义HTML字符
|
||
siteName = escapeHtml(siteName);
|
||
errorMsg = escapeHtml(errorMsg);
|
||
|
||
html += '<div style="padding: 6px 8px; margin-bottom: 6px; background: white; border: 1px solid #f8d7da; border-radius: 4px; transition: all 0.2s;">';
|
||
html += '<div>';
|
||
html += '<div style="display: flex; align-items: center; margin-bottom: 4px;">';
|
||
html += '<span style="font-weight: 500; color: #721c24; font-size: 11px;">' + siteName + '</span>';
|
||
html += '<span style="margin-left: 8px; color: #dc3545; font-size: 10px; background: #f8d7da; padding: 1px 4px; border-radius: 2px;">' + errorMsg + '</span>';
|
||
html += '</div>';
|
||
if (site.url) {
|
||
html += '<div style="color: #6c757d; font-size: 10px; word-break: break-all; line-height: 1.4;">' + escapeHtml(String(site.url)) + '</div>';
|
||
}
|
||
html += '</div>';
|
||
html += '</div>';
|
||
});
|
||
|
||
html += '</div>';
|
||
html += '</div>';
|
||
}
|
||
// 不再显示纯统计信息,避免重复难看
|
||
}
|
||
|
||
html += '</div>';
|
||
});
|
||
}
|
||
|
||
console.log('生成的HTML长度:', html.length);
|
||
$('#status-cron-logs').html(html);
|
||
console.log('更新完成');
|
||
|
||
} catch (error) {
|
||
console.error('更新状态检查日志时出错:', error);
|
||
$('#status-cron-logs').html('<div class="cron-log-item">显示日志时出错: ' + error.message + '</div>');
|
||
}
|
||
}
|
||
// 新增:将任意时间转换为北京时间
|
||
function convertToBeijingTime(timeString) {
|
||
var date = new Date(timeString);
|
||
|
||
// 如果已经是无效日期,返回原始值
|
||
if (isNaN(date.getTime())) {
|
||
return timeString;
|
||
}
|
||
|
||
// 将UTC时间转换为北京时间(UTC+8)
|
||
// 方法1:如果服务器时间是UTC时间
|
||
var beijingTime = new Date(date.getTime() + (8 * 60 * 60 * 1000));
|
||
|
||
// 方法2:更精确的转换(处理夏令时等)
|
||
// var beijingTime = new Date(date.toLocaleString('en-US', { timeZone: 'Asia/Shanghai' }));
|
||
|
||
return beijingTime;
|
||
}
|
||
|
||
// 新增:格式化日期时间
|
||
function formatDateTime(date) {
|
||
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
||
return '无效时间';
|
||
}
|
||
|
||
var year = date.getFullYear();
|
||
var month = String(date.getMonth() + 1).padStart(2, '0');
|
||
var day = String(date.getDate()).padStart(2, '0');
|
||
var hours = String(date.getHours()).padStart(2, '0');
|
||
var minutes = String(date.getMinutes()).padStart(2, '0');
|
||
var seconds = String(date.getSeconds()).padStart(2, '0');
|
||
|
||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
|
||
}
|
||
|
||
// 显示加载动画
|
||
function showLoading() {
|
||
$('#loading-overlay').fadeIn(200);
|
||
}
|
||
|
||
// 隐藏加载动画
|
||
function hideLoading() {
|
||
$('#loading-overlay').fadeOut(200);
|
||
}
|
||
|
||
// 显示消息
|
||
function showMessage(message, type) {
|
||
var alert = $('#message-alert');
|
||
alert.removeClass('success error warning info').addClass(type);
|
||
alert.html(message).fadeIn(300);
|
||
|
||
setTimeout(function() {
|
||
alert.fadeOut(300);
|
||
}, 3000);
|
||
}
|
||
|
||
// 更新获取RSS按钮状态
|
||
function updateFetchRssButton() {
|
||
var btn = $('#fetch-rss-btn');
|
||
var text = $('.fetch-rss-text');
|
||
var spinner = $('.fetching-spinner');
|
||
|
||
if (isFetchingRss) {
|
||
btn.prop('disabled', true);
|
||
text.text('获取中...');
|
||
spinner.show();
|
||
} else {
|
||
btn.prop('disabled', false);
|
||
text.text('获取网站信息');
|
||
spinner.hide();
|
||
}
|
||
}
|
||
|
||
// 从URL获取网站信息和RSS地址
|
||
function fetchRssFromUrl() {
|
||
var url = $('#url-url').val().trim();
|
||
var isEditMode = !!$('#url-id').val(); // 检测是否是编辑模式
|
||
|
||
if (!url) {
|
||
showMessage('请先填写网站地址', 'warning');
|
||
$('#url-url').focus();
|
||
return;
|
||
}
|
||
|
||
// 验证URL格式
|
||
var urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
|
||
if (!urlPattern.test(url)) {
|
||
showMessage('网站地址格式无效', 'warning');
|
||
return;
|
||
}
|
||
|
||
// 确保URL有协议前缀
|
||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||
url = 'https://' + url;
|
||
$('#url-url').val(url);
|
||
}
|
||
|
||
isFetchingRss = true;
|
||
updateFetchRssButton();
|
||
$('#rss-autofetch-section').show();
|
||
$('#rss-urls-list').html('<div class="rss-url-item" style="text-align: center; padding: 20px; color: #666;">正在获取网站信息...</div>');
|
||
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'GET',
|
||
data: {do: 'fetchRss', url: url},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
isFetchingRss = false;
|
||
updateFetchRssButton();
|
||
|
||
if (response.success) {
|
||
// 1. 自动填充网站标题(如果是新增模式或者用户确认)
|
||
var currentTitle = $('#url-title').val();
|
||
var shouldUpdateTitle = false;
|
||
|
||
if (isEditMode) {
|
||
// 编辑模式下询问用户是否更新标题
|
||
if (currentTitle !== response.siteInfo.title && response.siteInfo.title) {
|
||
if (confirm('检测到网站标题与当前不同:\n当前: ' + currentTitle + '\n新标题: ' + response.siteInfo.title + '\n\n是否更新网站标题?')) {
|
||
shouldUpdateTitle = true;
|
||
}
|
||
}
|
||
} else {
|
||
// 新增模式下直接更新
|
||
shouldUpdateTitle = true;
|
||
}
|
||
|
||
if (shouldUpdateTitle && response.siteInfo && response.siteInfo.title) {
|
||
$('#url-title').val(response.siteInfo.title);
|
||
}
|
||
|
||
// 2. 自动填充网站描述(如果为空或用户确认)
|
||
var currentDescription = $('#url-description').val();
|
||
if (response.siteInfo && response.siteInfo.description) {
|
||
if (!currentDescription || currentDescription === '-') {
|
||
// 描述为空,直接填充
|
||
$('#url-description').val(response.siteInfo.description);
|
||
} else if (isEditMode) {
|
||
// 编辑模式下,如果描述不同,询问用户
|
||
if (currentDescription !== response.siteInfo.description) {
|
||
if (confirm('检测到网站描述与当前不同,是否更新网站描述?')) {
|
||
$('#url-description').val(response.siteInfo.description);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. 显示RSS地址(如果有的话)
|
||
if (response.hasRss && response.rssUrls && response.rssUrls.length > 0) {
|
||
var html = '';
|
||
response.rssUrls.forEach(function(rssUrl, index) {
|
||
var type = 'RSS';
|
||
if (rssUrl.includes('.atom')) type = 'ATOM';
|
||
if (rssUrl.includes('.rdf')) type = 'RDF';
|
||
|
||
html += '<div class="rss-url-item" data-url="' + rssUrl + '" onclick="selectRssUrl(this)">';
|
||
html += '<div class="url">' + rssUrl + '</div>';
|
||
html += '<div><span class="type">' + type + '</span></div>';
|
||
html += '</div>';
|
||
});
|
||
$('#rss-urls-list').html(html);
|
||
|
||
// 检查是否已经有RSS地址
|
||
var currentRssUrl = $('#url-rss-url').val();
|
||
if (!currentRssUrl && response.rssUrls.length === 1) {
|
||
// 如果没有RSS地址且只有一个选项,自动选择
|
||
$('#url-rss-url').val(response.rssUrls[0]);
|
||
showMessage('已获取网站信息,并自动选择一个RSS地址', 'success');
|
||
} else if (isEditMode && currentRssUrl && response.rssUrls.includes(currentRssUrl)) {
|
||
// 编辑模式下,如果当前RSS地址在找到的列表中
|
||
showMessage('成功获取网站信息,当前RSS地址有效', 'success');
|
||
} else {
|
||
showMessage('成功获取网站信息,找到 ' + response.rssUrls.length + ' 个RSS地址,请点击选择', 'success');
|
||
}
|
||
} else {
|
||
$('#rss-urls-list').html('<div class="rss-url-item" style="text-align: center; padding: 20px; color: #666;">未检测到RSS地址,请手动填写</div>');
|
||
if (isEditMode) {
|
||
showMessage('成功获取网站信息,但未找到新的RSS地址', 'info');
|
||
} else {
|
||
showMessage('成功获取网站信息,但未找到RSS地址', 'info');
|
||
}
|
||
}
|
||
} else {
|
||
$('#rss-urls-list').html('<div class="rss-url-item" style="text-align: center; padding: 20px; color: #666;">获取失败:' + (response.message || '未知错误') + '</div>');
|
||
showMessage('获取网站信息失败:' + response.message, 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
isFetchingRss = false;
|
||
updateFetchRssButton();
|
||
$('#rss-urls-list').html('<div class="rss-url-item" style="text-align: center; padding: 20px; color: #666;">获取失败:网络错误</div>');
|
||
showMessage('获取网站信息失败:网络错误', 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 保存网址 - 修复版本,不自动检查状态
|
||
function saveUrl() {
|
||
var id = $('#url-id').val();
|
||
isEditMode = !!id;
|
||
|
||
// 验证表单
|
||
var title = $('#url-title').val().trim();
|
||
var url = $('#url-url').val().trim();
|
||
var rssUrl = $('#url-rss-url').val().trim();
|
||
var starRating = $('#url-star-rating').val(); // 获取星级值
|
||
|
||
if (!title) {
|
||
showMessage('请填写网站标题', 'warning');
|
||
$('#url-title').focus();
|
||
return;
|
||
}
|
||
|
||
if (!url) {
|
||
showMessage('请填写网站地址', 'warning');
|
||
$('#url-url').focus();
|
||
return;
|
||
}
|
||
|
||
// 如果是新增模式且没有RSS地址,显示提示
|
||
if (!isEditMode && !rssUrl) {
|
||
if (!confirm('未填写RSS地址,确定要继续保存吗?\n\n建议:\n1. 点击"获取网站信息"按钮自动获取\n2. 或稍后在编辑时补充RSS地址')) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 获取分类ID - 正确处理空值
|
||
var categorySelect = document.getElementById('url-category');
|
||
var categoryId = categorySelect ? categorySelect.value : '';
|
||
|
||
// 准备表单数据
|
||
var formData = {
|
||
do: isEditMode ? 'updateUrl' : 'addUrl',
|
||
title: title,
|
||
url: url,
|
||
description: $('#url-description').val().trim(),
|
||
sort_order: $('#url-sort').val() || 0,
|
||
star_rating: $('#url-star-rating').val() || '0' // 新增星级字段
|
||
};
|
||
|
||
// 添加RSS地址
|
||
if (rssUrl) {
|
||
formData.rss_url = rssUrl;
|
||
}
|
||
|
||
// 处理分类ID:如果是空字符串,不发送category_id参数
|
||
if (categoryId !== '') {
|
||
formData.category_id = categoryId;
|
||
}
|
||
|
||
if (isEditMode) {
|
||
formData.id = id;
|
||
}
|
||
|
||
console.log('保存网址数据:', formData);
|
||
console.log('操作类型:', isEditMode ? '编辑' : '新增');
|
||
|
||
// 新增和编辑保存后都不自动检查状态
|
||
console.log('保存操作,不触发状态检查');
|
||
|
||
showLoading();
|
||
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'POST',
|
||
data: formData,
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
hideLoading();
|
||
console.log('保存网址响应:', response);
|
||
if (response.success) {
|
||
showMessage(response.message, 'success');
|
||
hideUrlModal();
|
||
|
||
// 立即刷新页面,不等待
|
||
console.log('保存成功,立即刷新页面');
|
||
window.location.reload();
|
||
} else {
|
||
showMessage(response.message || '保存失败', 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
hideLoading();
|
||
console.error('保存网址错误:', status, error);
|
||
var errorMsg = '操作失败:';
|
||
|
||
try {
|
||
var responseText = xhr.responseText;
|
||
if (responseText.includes('FOREIGN KEY constraint failed')) {
|
||
errorMsg = '保存失败:所选分类不存在';
|
||
} else if (responseText.includes('标题已存在')) {
|
||
errorMsg = '保存失败:网站标题已存在';
|
||
} else if (responseText.includes('网站地址已存在')) {
|
||
errorMsg = '保存失败:网站地址已存在';
|
||
} else {
|
||
// 尝试解析JSON错误
|
||
var jsonResponse = JSON.parse(responseText);
|
||
errorMsg = jsonResponse.message || error;
|
||
}
|
||
} catch (e) {
|
||
errorMsg += error;
|
||
}
|
||
|
||
showMessage(errorMsg, 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 批量删除网址
|
||
function batchDeleteUrls() {
|
||
var selectedIds = [];
|
||
$('input[name="url[]"]:checked').each(function() {
|
||
selectedIds.push($(this).val());
|
||
});
|
||
|
||
if (selectedIds.length === 0) {
|
||
showMessage('请选择要删除的网址', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (confirm('你确认要删除选中的 ' + selectedIds.length + ' 个网址吗?')) {
|
||
showLoading();
|
||
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'POST',
|
||
data: {
|
||
do: 'batchDeleteUrls',
|
||
url: selectedIds
|
||
},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
hideLoading();
|
||
if (response.success) {
|
||
showMessage('成功删除 ' + selectedIds.length + ' 个网址', 'success');
|
||
setTimeout(function() {
|
||
window.location.reload();
|
||
}, 1500);
|
||
} else {
|
||
showMessage(response.message, 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
hideLoading();
|
||
showMessage('批量删除失败:' + (error || '网络错误'), 'error');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 保存分类 - 修复版本
|
||
function saveCategory() {
|
||
var id = $('#category-id').val();
|
||
isEditMode = !!id;
|
||
|
||
// 验证表单
|
||
var name = $('#category-name').val().trim();
|
||
if (!name) {
|
||
showMessage('请填写分类名称', 'warning');
|
||
$('#category-name').focus();
|
||
return;
|
||
}
|
||
|
||
// 准备表单数据
|
||
var formData = {
|
||
do: isEditMode ? 'updateCategory' : 'addCategory',
|
||
name: name,
|
||
description: $('#category-description').val().trim(),
|
||
sort_order: $('#category-sort').val() || 0
|
||
};
|
||
|
||
if (isEditMode) {
|
||
formData.id = id;
|
||
}
|
||
|
||
console.log('保存分类数据:', formData);
|
||
console.log('操作类型:', isEditMode ? '编辑' : '新增');
|
||
|
||
showLoading();
|
||
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'POST',
|
||
data: formData,
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
hideLoading();
|
||
console.log('保存分类响应:', response);
|
||
if (response.success) {
|
||
showMessage(response.message, 'success');
|
||
hideCategoryModal();
|
||
|
||
// 立即刷新页面
|
||
console.log('分类保存成功,立即刷新页面');
|
||
window.location.reload();
|
||
} else {
|
||
showMessage(response.message || '保存失败', 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
hideLoading();
|
||
console.error('保存分类错误:', status, error);
|
||
|
||
var errorMsg = '操作失败:';
|
||
try {
|
||
var responseText = xhr.responseText;
|
||
if (responseText.includes('分类名称已存在')) {
|
||
errorMsg = '保存失败:分类名称已存在';
|
||
} else {
|
||
var jsonResponse = JSON.parse(responseText);
|
||
errorMsg = jsonResponse.message || error;
|
||
}
|
||
} catch (e) {
|
||
errorMsg += error;
|
||
}
|
||
|
||
showMessage(errorMsg, 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 批量删除分类
|
||
function batchDeleteCategories() {
|
||
var selectedIds = [];
|
||
$('input[name="category[]"]:checked').each(function() {
|
||
selectedIds.push($(this).val());
|
||
});
|
||
|
||
if (selectedIds.length === 0) {
|
||
showMessage('请选择要删除的分类', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (confirm('你确认要删除选中的 ' + selectedIds.length + ' 个分类吗?')) {
|
||
showLoading();
|
||
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'POST',
|
||
data: {
|
||
do: 'batchDeleteCategories',
|
||
category: selectedIds
|
||
},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
hideLoading();
|
||
if (response.success) {
|
||
showMessage('成功删除 ' + selectedIds.length + ' 个分类', 'success');
|
||
setTimeout(function() {
|
||
window.location.reload();
|
||
}, 1500);
|
||
} else {
|
||
showMessage(response.message, 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
hideLoading();
|
||
showMessage('批量删除失败:' + (error || '网络错误'), 'error');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 切换选项卡
|
||
function switchTab(tab) {
|
||
if (tab === currentTab) return;
|
||
|
||
$('.tab-button').removeClass('active');
|
||
$('.tab-button[data-tab="' + tab + '"]').addClass('active');
|
||
|
||
$('.tab-content').removeClass('active');
|
||
$('#' + tab + '-tab').addClass('active');
|
||
|
||
currentTab = tab;
|
||
|
||
// 切换标签页时同步全选状态
|
||
setTimeout(syncCheckAllState, 100);
|
||
}
|
||
|
||
// 修复后的 checkWebsiteStatus 函数
|
||
function checkWebsiteStatus() {
|
||
var selectedIds = [];
|
||
$('input[name="url[]"]:checked').each(function() {
|
||
selectedIds.push($(this).val());
|
||
});
|
||
|
||
var $btn = $('#check-status-btn');
|
||
$btn.find('.spinner').show();
|
||
$btn.find('.btn-text').text('检查中...');
|
||
$btn.prop('disabled', true);
|
||
|
||
// 创建进度显示容器(如果不存在)
|
||
var $progressContainer = $('#batch-progress-container');
|
||
if ($progressContainer.length === 0) {
|
||
$progressContainer = $('<div id="batch-progress-container" style="position: fixed; top: 100px; left: 50%; transform: translateX(-50%); z-index: 9999; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); min-width: 400px; text-align: center;"></div>');
|
||
$('body').append($progressContainer);
|
||
}
|
||
|
||
// 初始化进度显示 - 增加累计数据显示
|
||
$progressContainer.html(`
|
||
<h4 style="margin-top: 0; color: #467b96;">正在检查</h4>
|
||
<div id="progress-status" style="margin-bottom: 15px; color: #666;">准备中...</div>
|
||
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">
|
||
<div style="flex: 1; background: #e9ecef; height: 10px; border-radius: 5px; overflow: hidden;">
|
||
<div id="progress-bar" style="width: 0%; height: 100%; background: #28a745; transition: width 0.3s;"></div>
|
||
</div>
|
||
<div id="progress-text" style="font-size: 12px; color: #666;">0%</div>
|
||
</div>
|
||
<div id="progress-stats" style="font-size: 12px; color: #666; margin-bottom: 15px;">
|
||
本批网址: 0,成功: 0,失败: 0
|
||
</div>
|
||
<button id="progress-stop-btn" class="btn danger" style="font-size: 12px; padding: 6px 16px;text-align:center;">停止检查</button>
|
||
`);
|
||
$progressContainer.show();
|
||
|
||
// 初始化累计数据变量
|
||
var cumulativeData = {
|
||
total: 0,
|
||
success: 0,
|
||
failed: 0
|
||
};
|
||
|
||
// 停止按钮事件
|
||
$('#progress-stop-btn').off('click').on('click', function() {
|
||
if (confirm('确定要停止检查吗?已完成的检查结果将保留。')) {
|
||
$progressContainer.remove();
|
||
resetCheckButton($btn);
|
||
showMessage('检查已停止', 'warning');
|
||
}
|
||
});
|
||
|
||
// 准备检查数据 - 保持原有格式
|
||
var data = {
|
||
do: 'checkStatus',
|
||
batch_size: 10, // 每批10个
|
||
total_batch: 1,
|
||
current_batch: 0
|
||
};
|
||
|
||
// 计算总网址数用于进度条
|
||
var totalUrlsForProgress = 0;
|
||
var totalBatches = 1;
|
||
|
||
if (selectedIds.length > 0) {
|
||
// 检查选中的网址 - 修复:传递选中的ID
|
||
console.log('选中了' + selectedIds.length + '个网址进行检查:', selectedIds);
|
||
data.url_ids = selectedIds.join(',');
|
||
totalUrlsForProgress = selectedIds.length;
|
||
totalBatches = Math.ceil(selectedIds.length / 10);
|
||
data.total_urls = selectedIds.length;
|
||
data.total_batch = totalBatches;
|
||
data.check_type = 'selected';
|
||
} else {
|
||
// 检查所有网址
|
||
console.log('未选中任何网址,检查全部');
|
||
data.check_type = 'all';
|
||
totalUrlsForProgress = parseInt($('#stat-total').text()) || 0;
|
||
totalBatches = Math.ceil(totalUrlsForProgress / 10);
|
||
data.total_urls = totalUrlsForProgress;
|
||
data.total_batch = totalBatches;
|
||
// 重要:不传递url_ids参数,让后端检查全部
|
||
}
|
||
|
||
console.log('开始状态检查,参数:', data);
|
||
console.log('选中ID数:', selectedIds.length, '总网址数:', totalUrlsForProgress, '总批数:', totalBatches);
|
||
|
||
// 开始第一批检查
|
||
startBatchCheck(data, 1, $progressContainer, $btn, cumulativeData, totalUrlsForProgress, totalBatches, selectedIds);
|
||
}
|
||
|
||
// 新增:开始批次检查 - 修复批次停止逻辑
|
||
function startBatchCheck(data, batchNumber, $progressContainer, $btn, cumulativeData, totalUrlsForProgress, totalBatches, selectedIds) {
|
||
data.current_batch = batchNumber;
|
||
data.batch_number = batchNumber;
|
||
|
||
// 重要:确保选中的ID持续传递
|
||
if (selectedIds && selectedIds.length > 0) {
|
||
data.url_ids = selectedIds.join(',');
|
||
data.total_urls = selectedIds.length;
|
||
data.total_batch = Math.ceil(selectedIds.length / 10);
|
||
}
|
||
|
||
// 更新进度显示
|
||
var processedBeforeThisBatch = (batchNumber - 1) * 10; // 每批10个
|
||
var progressPercent = Math.round(processedBeforeThisBatch / totalUrlsForProgress * 100);
|
||
|
||
// 确保进度不超过100%
|
||
progressPercent = Math.min(progressPercent, 100);
|
||
|
||
$('#progress-bar').css('width', progressPercent + '%');
|
||
$('#progress-text').text(progressPercent + '%');
|
||
$('#progress-status').html(`
|
||
<span>正在检查第 <strong>${batchNumber}</strong> 批 / 总共 <strong>${totalBatches}</strong> 批</span>
|
||
<span id="progress-cumulative" style="font-size: 13px; color: #28a745; font-weight: 500;margin-left:15px;">
|
||
已查 <span id="cumulative-total">${cumulativeData.total}</span>个,成功 <span id="cumulative-success">${cumulativeData.success}</span>个,失败 <span id="cumulative-failed">${cumulativeData.failed}</span>个
|
||
</span>
|
||
`);
|
||
|
||
// 更新累计数据显示
|
||
$('#cumulative-total').text(cumulativeData.total);
|
||
$('#cumulative-success').text(cumulativeData.success);
|
||
$('#cumulative-failed').text(cumulativeData.failed);
|
||
|
||
console.log('发送第' + batchNumber + '批请求,选中的ID:', data.url_ids);
|
||
console.log('累计已查:', cumulativeData.total, '预期总数:', totalUrlsForProgress);
|
||
|
||
// 检查累计是否已超过或等于总数 - 新增停止条件
|
||
if (cumulativeData.total >= totalUrlsForProgress && batchNumber > 1) {
|
||
console.log('累计已查数已达到总数,停止检查');
|
||
completeCheck({success: true, message: '所有网址已检查完成'}, $progressContainer, $btn);
|
||
return;
|
||
}
|
||
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'POST',
|
||
data: data,
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
console.log('第' + batchNumber + '批检查响应:', response);
|
||
|
||
if (response.success) {
|
||
// 更新本批统计信息
|
||
updateProgressStats(response);
|
||
|
||
// 更新累计数据
|
||
cumulativeData.total += response.total || 0;
|
||
cumulativeData.success += response.success_count || 0;
|
||
cumulativeData.failed += response.failed_count || 0;
|
||
|
||
// 更新累计数据显示
|
||
$('#cumulative-total').text(cumulativeData.total);
|
||
$('#cumulative-success').text(cumulativeData.success);
|
||
$('#cumulative-failed').text(cumulativeData.failed);
|
||
|
||
// 更新表格中的状态指示器和检查时间
|
||
if (response.results) {
|
||
updateStatusInTable(response.results);
|
||
}
|
||
|
||
// 检查是否有更多批次 - 修复停止逻辑
|
||
var shouldContinue = false;
|
||
|
||
// 情况1:后端返回了明确的has_more
|
||
if (response.has_more !== undefined) {
|
||
shouldContinue = response.has_more === true;
|
||
}
|
||
// 情况2:累计数还没达到总数
|
||
else if (cumulativeData.total < totalUrlsForProgress) {
|
||
shouldContinue = true;
|
||
}
|
||
// 情况3:后端明确返回completed标志
|
||
else if (response.completed === true) {
|
||
shouldContinue = false;
|
||
}
|
||
// 情况4:批次号小于总批数,但累计已达总数
|
||
else if (batchNumber < totalBatches && cumulativeData.total >= totalUrlsForProgress) {
|
||
shouldContinue = false;
|
||
}
|
||
// 情况5:批次号小于总批数,累计未达总数
|
||
else if (batchNumber < totalBatches) {
|
||
shouldContinue = true;
|
||
}
|
||
|
||
console.log('批次判断:', {
|
||
batchNumber: batchNumber,
|
||
totalBatches: totalBatches,
|
||
cumulativeTotal: cumulativeData.total,
|
||
totalUrlsForProgress: totalUrlsForProgress,
|
||
has_more: response.has_more,
|
||
completed: response.completed,
|
||
shouldContinue: shouldContinue
|
||
});
|
||
|
||
if (shouldContinue) {
|
||
// 批次间延迟1秒,避免服务器压力过大
|
||
setTimeout(function() {
|
||
startBatchCheck(data, batchNumber + 1, $progressContainer, $btn, cumulativeData, totalUrlsForProgress, totalBatches, selectedIds);
|
||
}, 1000);
|
||
} else {
|
||
// 所有批次完成
|
||
completeCheck(response, $progressContainer, $btn);
|
||
}
|
||
|
||
// 每批完成后刷新统计
|
||
loadStatusStats();
|
||
|
||
} else {
|
||
// 当前批次失败
|
||
showMessage('第' + batchNumber + '批检查失败: ' + response.message, 'error');
|
||
|
||
// 是否继续下一批
|
||
if (confirm('当前批次检查失败,是否继续下一批?')) {
|
||
if (batchNumber < totalBatches) {
|
||
setTimeout(function() {
|
||
startBatchCheck(data, batchNumber + 1, $progressContainer, $btn, cumulativeData, totalUrlsForProgress, totalBatches, selectedIds);
|
||
}, 2000);
|
||
} else {
|
||
completeCheck(response, $progressContainer, $btn);
|
||
}
|
||
} else {
|
||
completeCheck(response, $progressContainer, $btn);
|
||
}
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
console.error('第' + batchNumber + '批请求错误:', error);
|
||
|
||
// 是否重试当前批次
|
||
if (confirm('第' + batchNumber + '批请求失败: ' + error + ',是否重试?')) {
|
||
setTimeout(function() {
|
||
startBatchCheck(data, batchNumber, $progressContainer, $btn, cumulativeData, totalUrlsForProgress, totalBatches, selectedIds);
|
||
}, 3000);
|
||
} else if (batchNumber < totalBatches) {
|
||
// 跳过当前批次,继续下一批
|
||
if (confirm('是否跳过当前批次,继续下一批?')) {
|
||
setTimeout(function() {
|
||
startBatchCheck(data, batchNumber + 1, $progressContainer, $btn, cumulativeData, totalUrlsForProgress, totalBatches, selectedIds);
|
||
}, 3000);
|
||
} else {
|
||
completeCheck(null, $progressContainer, $btn, '检查中断');
|
||
}
|
||
} else {
|
||
completeCheck(null, $progressContainer, $btn, '检查中断');
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 新增:更新进度统计(保持不变)
|
||
function updateProgressStats(response) {
|
||
var statsText = `本批网址: ${response.total || 0}个 | 成功: ${response.success_count || 0}个 | 失败: ${response.failed_count || 0}个`;
|
||
if (response.avg_response_time) {
|
||
statsText += ` | 响应时间: ${response.avg_response_time}MS`;
|
||
}
|
||
$('#progress-stats').html(statsText);
|
||
}
|
||
|
||
// 新增:完成检查(保持不变)
|
||
function completeCheck(response, $progressContainer, $btn, customMessage) {
|
||
// 更新最终进度
|
||
$('#progress-bar').css('width', '100%');
|
||
$('#progress-text').text('100%');
|
||
|
||
var message = customMessage || (response ? response.message : '检查完成');
|
||
$('#progress-status').html(`<strong>${message}</strong>`);
|
||
|
||
if (response && response.success) {
|
||
$('#progress-stats').css('color', '#28a745');
|
||
$('#progress-cumulative').css('color', '#28a745');
|
||
} else {
|
||
$('#progress-stats').css('color', '#dc3545');
|
||
$('#progress-cumulative').css('color', '#dc3545');
|
||
}
|
||
|
||
// 3秒后自动关闭进度窗口
|
||
setTimeout(function() {
|
||
$progressContainer.fadeOut(500, function() {
|
||
$(this).remove();
|
||
});
|
||
resetCheckButton($btn);
|
||
|
||
if (response && response.success) {
|
||
showMessage(message, 'success');
|
||
}
|
||
}, 3000);
|
||
}
|
||
|
||
// 新增:重置检查按钮状态
|
||
function resetCheckButton($btn) {
|
||
$btn.find('.spinner').hide();
|
||
$btn.find('.btn-text').text('检查网站状态');
|
||
$btn.prop('disabled', false);
|
||
}
|
||
|
||
// 更新表格中的状态显示和检查时间(不刷新页面)
|
||
function updateStatusInTable(results) {
|
||
var currentDate = new Date().toISOString().split('T')[0]; // 获取当前日期 YYYY-MM-DD
|
||
|
||
for (var urlId in results) {
|
||
var result = results[urlId];
|
||
var $row = $('#url-' + urlId);
|
||
|
||
if ($row.length) {
|
||
var $indicator = $row.find('.status-indicator');
|
||
if ($indicator.length) {
|
||
$indicator.removeClass('status-online status-offline status-unknown status-checking');
|
||
|
||
if (result.success) {
|
||
$indicator.addClass('status-online');
|
||
var title = '通连';
|
||
if (result.status_code && result.status_code > 0) {
|
||
title += ' (HTTP ' + result.status_code + ')';
|
||
}
|
||
if (result.response_time) {
|
||
title += ' - 响应: ' + result.response_time + 'ms';
|
||
}
|
||
title += ' - 最后检查: ' + currentDate;
|
||
$indicator.attr('title', title);
|
||
} else {
|
||
$indicator.addClass('status-offline');
|
||
var title = '失连: ' + (result.message || '访问失败');
|
||
if (result.status_code && result.status_code > 0) {
|
||
title += ' (HTTP ' + result.status_code + ')';
|
||
}
|
||
title += ' - 最后检查: ' + currentDate;
|
||
$indicator.attr('title', title);
|
||
}
|
||
}
|
||
|
||
// 更新检查时间列
|
||
var $checkTimeCell = $row.find('.check-time-cell');
|
||
if ($checkTimeCell.length) {
|
||
$checkTimeCell.html(currentDate);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 加载状态统计(包含RSS统计)
|
||
function loadStatusStats() {
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl,
|
||
type: 'GET',
|
||
data: {do: 'getStatusStats'},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
if (response.success) {
|
||
var stats = response.data;
|
||
$('#stat-total').text(stats.total);
|
||
$('#stat-online').text(stats.online);
|
||
$('#stat-offline').text(stats.offline);
|
||
$('#stat-unchecked').text(stats.unchecked);
|
||
$('#stat-rate').text(round(stats.online_rate)); // 改为整数
|
||
$('#stat-rss-yes').text(stats.has_rss);
|
||
$('#stat-rss-no').text(stats.no_rss);
|
||
|
||
// 更新统计面板样式
|
||
var $stats = $('#status-stats');
|
||
if (stats.online_rate >= 80) {
|
||
$stats.css({
|
||
'background': 'linear-gradient(135deg, #1f2937 0%, #111827 100%)',
|
||
'border-color': '#666'
|
||
});
|
||
} else if (stats.online_rate >= 60) {
|
||
$stats.css({
|
||
'background': 'linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%)',
|
||
'border-color': '#ffeaa7'
|
||
});
|
||
} else {
|
||
$stats.css({
|
||
'background': 'linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%)',
|
||
'border-color': '#f5c6cb'
|
||
});
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 四舍五入函数
|
||
function round(value) {
|
||
return Math.round(value);
|
||
}
|
||
|
||
// 显示导入模态框
|
||
function showImportModal() {
|
||
$('#import-form')[0].reset();
|
||
$('#import-preview').hide();
|
||
$('#import-modal-start').prop('disabled', true);
|
||
$('#import-modal-overlay').fadeIn(200);
|
||
$('#import-modal-container').fadeIn(200);
|
||
}
|
||
|
||
// 隐藏导入模态框
|
||
function hideImportModal() {
|
||
$('#import-modal-container').fadeOut(200);
|
||
$('#import-modal-overlay').fadeOut(200);
|
||
}
|
||
|
||
// 显示导入结果模态框
|
||
function showImportResultModal() {
|
||
$('#import-result-overlay').fadeIn(200);
|
||
$('#import-result-container').fadeIn(200);
|
||
}
|
||
|
||
// 隐藏导入结果模态框
|
||
function hideImportResultModal() {
|
||
$('#import-result-container').fadeOut(200);
|
||
$('#import-result-overlay').fadeOut(200);
|
||
}
|
||
|
||
// 预览文件内容
|
||
function previewFile(file) {
|
||
// 检查文件大小(限制为100KB用于预览)
|
||
if (file.size > 100 * 1024) {
|
||
$('#file-preview-content').text('文件过大,无法预览完整内容...');
|
||
$('#import-preview').show();
|
||
$('#import-modal-start').prop('disabled', false);
|
||
return;
|
||
}
|
||
|
||
var reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
try {
|
||
var content = e.target.result;
|
||
// 只显示前1000个字符用于预览
|
||
var preview = content.substring(0, 1000);
|
||
if (content.length > 1000) {
|
||
preview += '\n...(文件内容过长,已截断)';
|
||
}
|
||
$('#file-preview-content').text(preview);
|
||
$('#import-preview').show();
|
||
$('#import-modal-start').prop('disabled', false);
|
||
} catch (error) {
|
||
$('#file-preview-content').text('无法预览文件内容');
|
||
$('#import-preview').show();
|
||
$('#import-modal-start').prop('disabled', true);
|
||
}
|
||
};
|
||
reader.onerror = function() {
|
||
$('#file-preview-content').text('读取文件失败');
|
||
$('#import-preview').show();
|
||
$('#import-modal-start').prop('disabled', true);
|
||
};
|
||
reader.readAsText(file);
|
||
}
|
||
|
||
// 开始导入
|
||
function startImport() {
|
||
var fileInput = $('#opml-file')[0];
|
||
if (!fileInput.files || !fileInput.files[0]) {
|
||
showMessage('请选择要导入的OPML文件', 'warning');
|
||
return;
|
||
}
|
||
|
||
var file = fileInput.files[0];
|
||
var formData = new FormData();
|
||
formData.append('opml_file', file);
|
||
|
||
// 添加导入选项
|
||
formData.append('skip_duplicates', $('#import-form input[name="skip_duplicates"]').is(':checked') ? '1' : '0');
|
||
formData.append('auto_create_categories', $('#import-form input[name="auto_create_categories"]').is(':checked') ? '1' : '0');
|
||
|
||
showLoading();
|
||
|
||
$.ajax({
|
||
url: window.UrlNavConfig.actionUrl + '?do=importOpml',
|
||
type: 'POST',
|
||
data: formData,
|
||
processData: false,
|
||
contentType: false,
|
||
dataType: 'json',
|
||
timeout: 60000, // 60秒超时
|
||
success: function(response) {
|
||
hideLoading();
|
||
hideImportModal();
|
||
|
||
console.log('导入响应:', response);
|
||
|
||
if (response.success) {
|
||
showImportResult(response.data);
|
||
} else {
|
||
// 显示更详细的错误信息
|
||
var errorMsg = response.message || '未知错误';
|
||
if (response.debug) {
|
||
errorMsg += ' (' + response.debug + ')';
|
||
}
|
||
showMessage('导入失败: ' + errorMsg, 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
hideLoading();
|
||
console.error('导入错误:', status, error);
|
||
console.error('响应文本:', xhr.responseText);
|
||
|
||
var errorMsg = '导入失败: ';
|
||
if (status === 'timeout') {
|
||
errorMsg += '请求超时,请稍后重试';
|
||
} else if (status === 'error') {
|
||
try {
|
||
var response = JSON.parse(xhr.responseText);
|
||
errorMsg += response.message || xhr.statusText;
|
||
} catch (e) {
|
||
errorMsg += xhr.statusText || '网络错误';
|
||
}
|
||
} else {
|
||
errorMsg += error || '未知错误';
|
||
}
|
||
|
||
showMessage(errorMsg, 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 显示导入结果
|
||
function showImportResult(data) {
|
||
var html = '';
|
||
|
||
html += '<div style="margin-bottom: 20px;">';
|
||
html += '<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-bottom: 20px;">';
|
||
html += '<div style="background: #e8f5e9; padding: 15px; border-radius: 8px; text-align: center;">';
|
||
html += '<div style="font-size: 12px; color: #28a745; margin-bottom: 5px;">成功</div>';
|
||
html += '<div style="font-size: 24px; font-weight: bold; color: #28a745;">' + data.success + '</div>';
|
||
html += '</div>';
|
||
|
||
html += '<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; text-align: center;">';
|
||
html += '<div style="font-size: 12px; color: #6c757d; margin-bottom: 5px;">总数</div>';
|
||
html += '<div style="font-size: 24px; font-weight: bold; color: #467b96;">' + data.total + '</div>';
|
||
html += '</div>';
|
||
|
||
html += '<div style="background: #f8d7da; padding: 15px; border-radius: 8px; text-align: center;">';
|
||
html += '<div style="font-size: 12px; color: #dc3545; margin-bottom: 5px;">失败</div>';
|
||
html += '<div style="font-size: 24px; font-weight: bold; color: #dc3545;">' + data.failed + '</div>';
|
||
html += '</div>';
|
||
html += '</div>';
|
||
|
||
html += '<div style="display: flex; justify-content: space-between; margin-bottom: 15px; flex-wrap: wrap; gap: 10px;">';
|
||
html += '<span style="background: #d4edda; color: #155724; padding: 5px 10px; border-radius: 4px; font-size: 12px;">创建分类: ' + data.categories_created + '</span>';
|
||
html += '<span style="background: #d1ecf1; color: #0c5460; padding: 5px 10px; border-radius: 4px; font-size: 12px;">添加网址: ' + data.urls_added + '</span>';
|
||
html += '<span style="background: #fff3cd; color: #856404; padding: 5px 10px;border-radius: 4px; font-size: 12px;">跳过重复: ' + data.urls_skipped + '</span>';
|
||
html += '</div>';
|
||
html += '</div>';
|
||
|
||
if (data.details && data.details.length > 0) {
|
||
html += '<div style="max-height: 300px; overflow-y: auto;">';
|
||
html += '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
|
||
html += '<thead>';
|
||
html += '<tr style="background: #f8f9fa; border-bottom: 2px solid #dee2e6;">';
|
||
html += '<th style="padding: 10px; text-align: left;color: #000000;">类型</th>';
|
||
html += '<th style="padding: 10px; text-align: left;color: #000000;">名称</th>';
|
||
html += '<th style="padding: 10px; text-align: left;color: #000000;">状态</th>';
|
||
html += '<th style="padding: 10px; text-align: left;color: #000000;">说明</th>';
|
||
html += '</tr>';
|
||
html += '</thead>';
|
||
html += '<tbody>';
|
||
|
||
data.details.forEach(function(item) {
|
||
var statusColor = '';
|
||
if (item.status === 'added' || item.status === 'created') {
|
||
statusColor = 'color: #28a745;';
|
||
} else if (item.status === 'skipped') {
|
||
statusColor = 'color: #ffc107;';
|
||
} else {
|
||
statusColor = 'color: #dc3545;';
|
||
}
|
||
|
||
html += '<tr style="border-bottom: 1px solid #f0f2f5;">';
|
||
html += '<td style="padding: 10px;color: #000000;">' + (item.type === 'category' ? '分类' : '网址') + '</td>';
|
||
html += '<td style="padding: 10px; color: #000000;">' + (item.title || item.name || '') + '</td>';
|
||
html += '<td style="padding: 10px; ' + statusColor + ' font-weight: 500;">';
|
||
html += item.status === 'added' ? '已添加' :
|
||
item.status === 'created' ? '已创建' :
|
||
item.status === 'skipped' ? '已跳过' : '失败';
|
||
html += '</td>';
|
||
html += '<td style="padding: 10px; color: #666;">' + (item.message || '') + '</td>';
|
||
html += '</tr>';
|
||
});
|
||
|
||
html += '</tbody>';
|
||
html += '</table>';
|
||
html += '</div>';
|
||
}
|
||
|
||
$('#import-result-content').html(html);
|
||
showImportResultModal();
|
||
}
|
||
|
||
// 页面加载完成后初始化
|
||
$(document).ready(function() {
|
||
initUrlNav();
|
||
|
||
// 初始化剪贴板功能
|
||
initClipboard();
|
||
|
||
// 刷新状态检查定时任务统计
|
||
$('#refresh-status-cron-stats').on('click', function(e) {
|
||
e.preventDefault();
|
||
loadStatusCronStats();
|
||
showMessage('正在刷新状态检查定时任务统计...', 'info');
|
||
});
|
||
});
|
||
|
||
})(jQuery);
|
||
|
||
// 初始化剪贴板功能
|
||
function initClipboard() {
|
||
// 使用现代的 Clipboard API
|
||
$(document).on('click', '.copy-btn', function(e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
var targetId = $(this).data('clipboard-target');
|
||
if (!targetId) {
|
||
targetId = '#status-cron-url';
|
||
}
|
||
var inputElement = $(targetId)[0];
|
||
|
||
if (!inputElement) {
|
||
showMessage('找不到要复制的内容', 'error');
|
||
return;
|
||
}
|
||
|
||
// 现代剪贴板 API
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
navigator.clipboard.writeText(inputElement.value)
|
||
.then(() => {
|
||
showMessage('已复制到剪贴板', 'success');
|
||
// 添加视觉反馈
|
||
var btn = $(this);
|
||
var originalText = btn.text();
|
||
btn.text('已复制');
|
||
btn.css('background', '#28a745').css('color', 'white');
|
||
setTimeout(function() {
|
||
btn.text(originalText);
|
||
btn.css('background', '').css('color', '');
|
||
}, 1500);
|
||
})
|
||
.catch(err => {
|
||
console.error('复制失败:', err);
|
||
// 降级到传统方法
|
||
fallbackCopyText(inputElement, this);
|
||
});
|
||
} else {
|
||
// 降级到传统方法
|
||
fallbackCopyText(inputElement, this);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 传统复制方法
|
||
function fallbackCopyText(inputElement, buttonElement) {
|
||
inputElement.select();
|
||
inputElement.setSelectionRange(0, 99999); // For mobile devices
|
||
|
||
try {
|
||
var successful = document.execCommand('copy');
|
||
if (successful) {
|
||
showMessage('已复制到剪贴板', 'success');
|
||
// 添加视觉反馈
|
||
if (buttonElement) {
|
||
var originalText = buttonElement.textContent || $(buttonElement).text();
|
||
$(buttonElement).text('已复制');
|
||
$(buttonElement).css('background', '#28a745').css('color', 'white');
|
||
setTimeout(function() {
|
||
$(buttonElement).text(originalText);
|
||
$(buttonElement).css('background', '').css('color', '');
|
||
}, 1500);
|
||
}
|
||
} else {
|
||
showMessage('复制失败,请手动复制', 'warning');
|
||
}
|
||
} catch (err) {
|
||
console.error('复制失败:', err);
|
||
showMessage('复制失败: ' + err, 'error');
|
||
}
|
||
}
|
||
|
||
// 修复立即运行按钮的函数
|
||
function runStatusCronNow() {
|
||
if (!confirm('确定要立即运行状态检查定时任务吗?\n这将检查所有网站的状态。')) {
|
||
return;
|
||
}
|
||
|
||
showLoading();
|
||
|
||
// 使用正确的 action URL
|
||
var actionUrl = window.UrlNavConfig.actionUrl;
|
||
var secret = '<?php echo $pluginOptions->statusCronSecret; ?>';
|
||
|
||
// 确保 secret 不为空
|
||
if (!secret) {
|
||
showMessage('定时任务密钥未配置,请先设置密钥', 'error');
|
||
hideLoading();
|
||
return;
|
||
}
|
||
|
||
console.log('立即执行状态检查,secret:', secret.substring(0, 10) + '...');
|
||
|
||
// 使用 POST 请求并发送正确的参数
|
||
$.ajax({
|
||
url: actionUrl,
|
||
type: 'POST',
|
||
data: {
|
||
do: 'statusCron',
|
||
secret: secret
|
||
},
|
||
dataType: 'json',
|
||
timeout: 30000, // 30秒超时
|
||
success: function(response) {
|
||
hideLoading();
|
||
console.log('定时任务执行响应:', response);
|
||
|
||
if (response && response.success) {
|
||
var message = '状态检查定时任务执行成功';
|
||
if (response.message) {
|
||
message += ': ' + response.message;
|
||
}
|
||
if (response.data) {
|
||
message += ' (' + response.data + ')';
|
||
}
|
||
showMessage(message, 'success');
|
||
|
||
// 重新加载统计和日志
|
||
setTimeout(function() {
|
||
loadStatusCronStats();
|
||
}, 1000);
|
||
|
||
} else {
|
||
var errorMsg = '执行失败: ' + (response ? response.message : '未知错误');
|
||
showMessage(errorMsg, 'error');
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
hideLoading();
|
||
console.error('定时任务执行错误:', status, error);
|
||
|
||
var errorMsg = '执行请求失败: ';
|
||
if (status === 'timeout') {
|
||
errorMsg += '请求超时,可能任务正在执行中';
|
||
} else if (xhr.status === 403) {
|
||
errorMsg += '密钥验证失败';
|
||
} else if (xhr.responseText) {
|
||
try {
|
||
var response = JSON.parse(xhr.responseText);
|
||
errorMsg = response.message || xhr.statusText;
|
||
} catch (e) {
|
||
errorMsg += xhr.statusText || '网络错误';
|
||
}
|
||
} else {
|
||
errorMsg += error;
|
||
}
|
||
|
||
showMessage(errorMsg, 'error');
|
||
}
|
||
});
|
||
}
|
||
</script>
|
||
|
||
<?php
|
||
include 'footer.php';
|
||
?>
|