Files
UrlNav/Rss.php

5121 lines
179 KiB
PHP
Raw Normal View History

2026-02-23 20:15:55 +08:00
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
// 包含必要的文件
include 'header.php';
include 'menu.php';
// 获取插件选项
$options = Typecho_Widget::widget('Widget_Options');
$pluginConfig = UrlNav_Plugin::getConfig();
// 获取参数
$currentPage = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$categoryId = isset($_GET['category']) ? $_GET['category'] : 'all';
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
$showFavorites = isset($_GET['favorites']) && $_GET['favorites'] == '1';
// 修复这里:转换旧值到新值
$starRating = isset($_GET['star']) ? $_GET['star'] : 'all';
// 转换旧值到新值
if ($starRating === 'none') {
$starRating = '0';
} elseif ($starRating === 'has') {
$starRating = 'starred';
}
// 获取每页显示数量
$pageSize = isset($pluginConfig->rssPageSize) ? max(1, intval($pluginConfig->rssPageSize)) : 20;
// 获取RSS信息支持分页和筛选
if ($showFavorites) {
// 获取收藏列表
$feedsData = UrlNav_Plugin::getFavorites(0, $currentPage, $pageSize, $search);
if ($feedsData['success']) {
$feeds = $feedsData['data'];
$totalFeeds = $feedsData['total'];
$totalPages = $feedsData['totalPages'];
} else {
$feeds = [];
$totalFeeds = 0;
$totalPages = 0;
}
} else {
// 获取正常RSS列表
$feedsData = UrlNav_Plugin::getAllRssFeeds($categoryId, $currentPage, $pageSize, $search, $starRating);
$feeds = $feedsData['data'];
$totalFeeds = $feedsData['total'];
$totalPages = $feedsData['totalPages'];
}
// 获取有RSS的网址数量
$rssUrlCount = UrlNav_Plugin::getTotalUrlsWithRss();
// 获取所有分类
$categories = UrlNav_Plugin::getAllCategories();
// 获取所有星级假设有0-3星级0表示无星级
$allStarRatings = [
['value' => 'all', 'label' => '全部星级'],
['value' => 'starred', 'label' => '有星级'], // 删除 'has',改为 'starred'
['value' => '0', 'label' => '无星级'], // 删除 'none',改为 '0'
['value' => '1', 'label' => '⭐'],
['value' => '2', 'label' => '⭐⭐ '],
['value' => '3', 'label' => '⭐⭐⭐']
];
// 获取缓存统计信息
$cacheStats = UrlNav_Plugin::getCacheStats();
// 获取RSS定时任务统计
$rssCronStats = UrlNav_Plugin::getRssCronStats();
// 获取收藏统计
$favoriteStats = UrlNav_Plugin::getFavoriteStats(0);
// 检查RSS定时任务锁
$rssLockFile = __DIR__ . '/db/rss_cron.lock';
$isRssLocked = false;
$rssLockTime = '';
$rssLockDuration = 0;
if (file_exists($rssLockFile)) {
$rssLockTime = @filemtime($rssLockFile);
if ($rssLockTime) {
$isRssLocked = true;
$rssLockDuration = time() - $rssLockTime;
}
}
// 转换为北京时间函数(修复版)
function convertToBeijingTime($datetime) {
if (empty($datetime) || $datetime == '0000-00-00 00:00:00' || $datetime == '0000-00-00 00:00') {
return '';
}
try {
// 创建DateTime对象
$date = new DateTime($datetime);
// 如果已经是北京时间,直接返回
if ($date->getTimezone()->getName() == 'Asia/Shanghai') {
return $date->format('Y-m-d H:i:s');
}
// 转换为北京时间
$date->setTimezone(new DateTimeZone('Asia/Shanghai'));
return $date->format('Y-m-d H:i:s');
} catch (Exception $e) {
// 如果解析失败,尝试简单的时间转换
$timestamp = strtotime($datetime);
if ($timestamp !== false) {
// 服务器时间已经是北京时间,直接返回
return date('Y-m-d H:i:s', $timestamp);
}
return $datetime;
}
}
// 格式化时间为易读格式(北京时间)
function formatTimeAgo($datetime) {
if (empty($datetime)) {
return '';
}
// 转换为北京时间的时间戳
$date = new DateTime($datetime);
$date->setTimezone(new DateTimeZone('Asia/Shanghai'));
$beijingTimestamp = $date->getTimestamp();
$currentTimestamp = time();
$diff = $currentTimestamp - $beijingTimestamp;
if ($diff < 60) {
return '刚刚';
} elseif ($diff < 3600) {
$minutes = floor($diff / 60);
return $minutes . '分钟前';
} elseif ($diff < 86400) {
$hours = floor($diff / 3600);
return $hours . '小时前';
} elseif ($diff < 2592000) {
$days = floor($diff / 86400);
return $days . '天前';
} elseif ($diff < 31536000) {
$months = floor($diff / 2592000);
return $months . '个月前';
} else {
$years = floor($diff / 31536000);
return $years . '年前';
}
}
// 获取北京时间显示
function formatBeijingDateTime($datetime, $format = 'Y-m-d H:i:s') {
if (empty($datetime)) {
return '';
}
$date = new DateTime($datetime);
$date->setTimezone(new DateTimeZone('Asia/Shanghai'));
return $date->format($format);
}
// 简化时间显示
function formatSimpleTime($datetime) {
return formatBeijingDateTime($datetime, 'm-d H:i');
}
// 格式化统计时间(北京时间)
function formatStatsTime($datetime) {
if (empty($datetime)) {
return '从未更新';
}
$date = new DateTime($datetime);
$date->setTimezone(new DateTimeZone('Asia/Shanghai'));
return $date->format('Y-m-d H:i');
}
// 获取RSS定时任务URL
function getRssCronUrl() {
global $pluginConfig, $options;
$baseUrl = $options->siteUrl;
if (substr($baseUrl, -1) == '/') {
$baseUrl = substr($baseUrl, 0, -1);
}
return $baseUrl . '/urlnav-rss-cron?secret=' . urlencode($pluginConfig->rssCronSecret ?? '');
}
// 文章内容摘要处理
function getArticleExcerpt($content, $length = 180) {
if (empty($content)) {
return '';
}
// 移除HTML标签
$text = strip_tags($content);
// 移除多余的空格和换行
$text = preg_replace('/\s+/', ' ', $text);
$text = trim($text);
// 截取指定长度
if (mb_strlen($text, 'UTF-8') > $length) {
$text = mb_substr($text, 0, $length, 'UTF-8') . '...';
}
return $text;
}
// 检查文章是否有完整内容
function hasFullContent($feed) {
return !empty($feed['full_content']) && trim($feed['full_content']) !== '';
}
?>
<style>
.filter-group a:hover{text-decoration:none!important;}
.typecho-page-main{padding:10px!important;}
.container{padding:0px!important;width:1200!important;}
/* 星级样式 */
.star-link {
text-decoration: none !important;
transition: all 0.2s ease;
}
.star-link:hover {
color: #ff9800 !important;
transform: scale(1.1);
}
.no-star-link {
text-decoration: none !important;
transition: all 0.2s ease;
}
.no-star-link:hover {
color: #495057 !important;
text-decoration: underline !important;
}
.star-rating-section {
display: flex;
align-items: center;
gap: 3px;
}
.star-filter-option {
display: flex;
align-items: center;
gap: 5px;
}
/* 状态相关样式 - 优化版 */
.feed-status-cell {
text-align: center;
padding: 10px 5px !important;
width: 30px;
cursor: pointer;
padding-left:20px!important;
}
.status-selector {
position: relative;
display: inline-block;
cursor: pointer;
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.status-selector:hover {
background-color: #f8f9fa;
transform: scale(1.1);
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
transition: all 0.2s ease;
position: relative;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.8);
border: 1px solid rgba(0, 0, 0, 0.1);
}
.status-dot::after {
content: '';
position: absolute;
top: -3px;
left: -3px;
right: -3px;
bottom: -3px;
border-radius: 50%;
opacity: 0;
transition: opacity 0.2s ease;
}
.status-selector:hover .status-dot {
transform: scale(1.3);
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.9);
}
.status-selector:hover .status-dot::after {
opacity: 0.1;
}
.status-unread {
background-color: #000;
}
.status-unread::after {
background-color: #dc3545;
}
.status-read {
background-color: #28a745;
}
.status-read::after {
background-color: #28a745;
}
.status-ignore {
background-color: #c82333;
}
.status-ignore::after {
background-color: #c82333;
}
/* 状态下拉菜单 */
.status-dropdown {
display: none;
position: absolute;
top: 100%;
left: 55%;
transform: translateX(-50%);
background: white;
border: 1px solid #e8ebf0;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.12);
width: 50px;
z-index: 1000;
padding: 8px 0;
margin-top: 8px;
}
.status-selector.active .status-dropdown {
display: block;
}
.status-dropdown::before {
content: '';
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid white;
z-index: 1;
}
.status-dropdown::after {
content: '';
position: absolute;
top: -9px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 9px solid transparent;
border-right: 9px solid transparent;
border-bottom: 9px solid #e8ebf0;
z-index: 0;
}
.status-option {
padding: 8px 16px;
display: flex;
align-items: center;
gap: 10px;
justify-content: center;
font-size: 13px;
color: #495057;
transition: all 0.2s;
cursor: pointer;
position: relative;
}
.status-option:hover {
background-color: #f8f9fa;
color: #467b96;
}
.status-option::before {
content: '';
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
}
.status-option[data-status="unread"]::before {
background-color: #000;
}
.status-option[data-status="read"]::before {
background-color: #28a745;
}
.status-option[data-status="ignore"]::before {
background-color: #c82333;
}
/* 行状态样式 - 只影响状态列,不影响整行 */
.rss-feeds-table tbody tr .feed-status-cell.status-read {
opacity: 0.8;
}
.rss-feeds-table tbody tr .feed-status-cell.status-read .status-dot {
background-color: #28a745;
}
.rss-feeds-table tbody tr .feed-status-cell.status-ignore {
opacity: 0.6;
}
.rss-feeds-table tbody tr .feed-status-cell.status-ignore .status-dot {
background-color: #6c757d;
}
/* 行选中状态 */
.rss-feeds-table tbody tr.selected {
background-color: #e3f2fd !important;
}
.rss-feeds-table tbody tr.selected td {
background-color: #e3f2fd !important;
}
/* 状态筛选器 - 改为点击展开的下拉菜单 */
.status-filter-dropdown {
position: relative;
display: inline-block;
}
.status-filter-btn {
padding: 8px 15px;
border: 1px solid #ced4da;
background: white;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
color: #000;
transition: all 0.2s;
display: flex;
height: 40px;
align-items: center;
gap: 8px;
min-width: 120px;
justify-content: space-between;
}
.status-filter-btn:hover {
background: #f8f9fa;
border-color: #467b96;
}
.status-filter-btn::after {
content: '▼';
font-size: 10px;
margin-left: 5px;
opacity: 0.7;
}
.status-filter-btn.active {
background: #467b96;
border-color: #467b96;
color: white;
}
.status-filter-btn.active::after {
color: white;
}
/* 状态筛选下拉菜单 - 点击展开 */
.status-filter-dropdown-content {
display: none;
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid #e8ebf0;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.12);
min-width: 120px;
z-index: 1000;
padding: 8px 0;
margin-top: 5px;
}
.status-filter-dropdown.active .status-filter-dropdown-content {
display: block;
}
.status-filter-option {
padding: 8px 16px;
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
color: #495057;
transition: all 0.2s;
cursor: pointer;
}
.status-filter-option:hover {
background-color: #f8f9fa;
color: #467b96;
}
.status-filter-option::before {
content: '';
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
}
.status-filter-option[data-status="all"]::before {
background-color: #467b96;
}
.status-filter-option[data-status="unread"]::before {
background-color: #dc3545;
}
.status-filter-option[data-status="read"]::before {
background-color: #28a745;
}
.status-filter-option[data-status="ignore"]::before {
background-color: #6c757d;
}
.status-filter-count {
margin-left: auto;
font-size: 11px;
color: #6c757d;
background: rgba(0,0,0,0.05);
padding: 1px 6px;
border-radius: 10px;
}
/* 批量操作下拉菜单 - 点击展开 */
.batch-dropdown {
position: relative;
display: inline-block;
}
.batch-dropdown-btn {
padding: 8px 15px;
border: 1px solid #ced4da;
background: white;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
color: #000;
height: 40px;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
min-width: 120px;
justify-content: space-between;
}
.batch-dropdown-btn:hover {
background: #f8f9fa;
border-color: #467b96;
}
.batch-dropdown-btn::after {
content: '▼';
font-size: 10px;
margin-left: 5px;
opacity: 0.7;
}
.batch-dropdown-btn.active {
background: #467b96;
border-color: #467b96;
color: white;
}
.batch-dropdown-btn.active::after {
color: white;
}
.batch-dropdown-content {
display: none;
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid #e8ebf0;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.12);
min-width: 120px;
z-index: 1000;
padding: 8px 0;
margin-top: 5px;
}
.batch-dropdown.active .batch-dropdown-content {
display: block;
}
.batch-option {
padding: 8px 16px;
font-size: 13px;
color: #495057;
transition: all 0.2s;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.batch-option:hover {
background-color: #f8f9fa;
color: #467b96;
}
.batch-option.select-all::before {
content: '✓';
color: #467b96;
}
.batch-option.clear-selection::before {
content: '○';
color: #6c757d;
}
.batch-option.mark-read::before {
content: '●';
color: #28a745;
}
.batch-option.mark-unread::before {
content: '●';
color: #dc3545;
}
.batch-option.mark-ignore::before {
content: '●';
color: #6c757d;
}
.batch-option.export-status::before {
content: '↓';
color: #467b96;
font-size: 12px;
width: 12px;
text-align: center;
line-height: 1;
font-weight: bold;
}
.batch-option.import-status::before {
content: '↑';
color: #28a745;
font-size: 12px;
width: 12px;
text-align: center;
line-height: 1;
font-weight: bold;
}
.batch-option.clear-status::before {
content: '×';
color: #dc3545;
font-size: 14px;
width: 12px;
text-align: center;
line-height: 1;
font-weight: bold;
}
#full-content-original-title{display:none;}
#full-content-site-title{display:none;}
/* 添加保存按钮样式 */
.action-btn.save-btn {
background: #17a2b8;
border-color: #17a2b8;
color: #fff;
}
.action-btn.save-btn:hover {
background: #138496;
border-color: #138496;
color: #fff;
}
#modal-title{color:#000!important;}
/**顶部标题栏干掉***/
.typecho-page-title{display:none;}
/* RSS信息阅读样式 - 优化版 */
.rss-feeds-container {
padding: 10px;
min-height: calc(100vh - 120px);
}
.rss-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 22px 25px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
border: 1px solid #e8ebf0;
}
.rss-title {
margin: 0;
font-size: 22px;
color: #2c3e50;
font-weight: 600;
line-height: 1.3;
}
.rss-stats {
color: #6c757d;
font-size: 14px;
margin-top: 8px;
line-height: 1.5;
}
.rss-stats strong {
color: #467b96;
font-weight: 600;
}
.rss-refresh-btn {
background: #28a745;
color: #FFF;
border: none;
padding: 10px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.25s ease;
box-shadow: 0 2px 4px rgba(40, 167, 69, 0.15);
}
.rss-refresh-btn:hover {
background: #218838;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(40, 167, 69, 0.2);
color: #000000;
}
.rss-refresh-btn:active {
transform: translateY(0);
box-shadow: 0 1px 3px rgba(40, 167, 69, 0.2);
}
.rss-refresh-btn:disabled {
background: #adb5bd;
cursor: not-allowed;
opacity: 0.7;
transform: none;
box-shadow: none;
color: #000000;
}
.rss-refresh-btn.manage-btn {
background: #467b96;
color: #fff;
padding: 8px 24px!important;
}
.rss-refresh-btn.manage-btn:hover {
background: #3a6a83;
box-shadow: 0 4px 8px rgba(70, 123, 150, 0.2);
color: #000000;
}
.rss-refresh-btn.clean-btn {
background: #dc3545;
color: #fff;
}
.rss-refresh-btn.clean-btn:hover {
background: #c82333;
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.2);
color: #fff;
}
.rss-refresh-btn.stats-btn {
background: #6f42c1;
color: #fff;
}
.rss-refresh-btn.stats-btn:hover {
background: #5a379f;
box-shadow: 0 4px 8px rgba(111, 66, 193, 0.2);
color: #000000;
}
.refreshing-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid rgba(0,0,0,.3);
border-radius: 50%;
border-top-color: #000000;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 筛选栏 */
.rss-filter-bar {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
align-items: center;
margin-bottom: 20px;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
border: 1px solid #e8ebf0;
}
.filter-group {
display: flex;
align-items: center;
gap: 10px;
}
.filter-label {
font-size: 14px;
color: #495057;
font-weight: 500;
white-space: nowrap;
}
.filter-select option{
text-align: center;
justify-content: center;
align-items: center;
}
.filter-select {
border: 1px solid #ced4da;
border-radius: 6px;
background: white;
color: #000000;
font-size: 14px;
min-width: 140px;
height: 40px;
cursor: pointer;
}
.filter-select:hover {
border-color: #adb5bd;
}
.filter-select:focus {
border-color: #467b96;
outline: none;
box-shadow: 0 0 0 3px rgba(70, 123, 150, 0.1);
}
.search-input {
padding: 8px 12px;
border: 1px solid #ced4da;
border-radius: 6px!important;
font-size: 14px;
min-width: 180px;
transition: border-color 0.2s;
height: 40px;
box-sizing: border-box;
color: #000000;
}
.search-input:focus {
border-color: #467b96;
outline: none;
box-shadow: 0 0 0 3px rgba(70, 123, 150, 0.1);
}
.search-btn {
padding: 8px 20px;
background: #467b96;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
height: 40px;
box-sizing: border-box;
}
.search-btn:hover {
background: #3a6a83;
transform: translateY(-1px);
color: #000000;
}
.search-btn.active {
background: #ffc107;
color: #000;
border-color: #ffc107;
}
.search-btn.active:hover {
background: #e0a800;
color: #000;
border-color: #e0a800;
}
.clear-btn {
padding: 8px 20px;
background: #6c757d;
color: #000000;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
text-decoration: none;
height: 40px;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
}
.clear-btn:hover {
background: #5a6268;
transform: translateY(-1px);
color: #000000;
text-decoration: none;
}
/* 分页样式 */
.rss-pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 20px;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
border: 1px solid #e8ebf0;
}
.pagination-btn {
padding: 8px 16px;
border: 1px solid #dee2e6;
background: white;
color: #467b96;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
text-decoration: none;
color: #000000;
}
.pagination-btn:hover:not(:disabled) {
background: #f8f9fa;
border-color: #467b96;
color: #000000;
text-decoration: none;
}
.pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination-current {
padding: 8px 16px;
background: #467b96;
color: #fff;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
border: 1px solid #467b96;
}
.pagination-ellipsis {
padding: 8px 12px;
color: #6c757d;
font-size: 14px;
}
.pagination-info {
color: #6c757d;
font-size: 14px;
margin-left: auto;
}
/* 表格容器 - 确保不超出边框 */
.table-container {
width: 100%;
overflow-x: auto;
border-radius: 10px;
background: white;
box-shadow: 0 3px 12px rgba(0,0,0,0.06);
margin-top: 20px;
border: 1px solid #e8ebf0;
}
.rss-feeds-table {
width: 100%;
border-collapse: collapse;
min-width: 900px; /* 确保表格有最小宽度 */
}
.rss-feeds-table th {
background: #f8fafc;
padding: 16px 20px;
text-align: left;
font-weight: 600;
color: #2c3e50;
border-bottom: 2px solid #e8ebf0;
font-size: 14px;
white-space: nowrap;
}
.rss-feeds-table td {
padding: 18px 20px;
border-bottom: 1px solid #f0f2f5;
vertical-align: top;
font-size: 14px;
transition: background-color 0.2s;
line-height: 1.5;
}
.rss-feeds-table tr:hover td {
background-color: #f8fafc;
}
.rss-feeds-table tr:last-child td {
border-bottom: none;
}
/* 序号 */
.feed-index {
text-align: center;
color: #6c757d;
font-weight: 500;
width: 20px;
font-size: 14px;
}
/* 分类标签 */
.category-tag {
display: inline-block;
padding: 4px 10px;
background: #e9f7fe;
color: #467b96;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
max-width: 26px;
margin-top: 4px;
border: 1px solid #b8d4ff;
}
/* 网站信息 */
.site-info {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 150px;
max-width: 150px;
}
.site-title {
font-weight: 600;
color: #2c3e50;
font-size: 15px;
line-height: 1.4;
word-break: break-word;
}
.site-url {
font-size: 12px;
color: #6c757d;
word-break: break-all;
line-height: 1.4;
}
.site-url a {
color: #467b96;
text-decoration: none;
transition: color 0.2s;
}
.site-url a:hover {
color: #3a6a83;
text-decoration: underline;
}
/* 文章信息 */
.article-info {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 300px;
max-width: 400px;
}
.article-title {
font-size: 15px;
line-height: 1.5;
margin: 0;
}
.article-title a {
color: #2c3e50;
text-decoration: none;
font-weight: 600;
transition: color 0.2s;
display: block;
word-break: break-word;
}
.article-title a:hover {
color: #467b96;
text-decoration: underline;
}
.article-description {
font-size: 13px;
color: #6c757d;
line-height: 1.6;
max-height: 40px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
word-break: break-word;
}
/* 时间信息 */
.time-info {
font-size: 13px;
color: #6c757d;
min-width: 140px;
max-width: 160px;
}
.pub-date {
color: #2c3e50;
font-weight: 500;
margin-bottom: 6px;
font-size: 14px;
word-break: break-word;
}
.time-ago {
color: #28a745;
font-size: 12px;
margin-bottom: 5px;
border-radius: 12px;
display: inline-block;
font-weight: 500;
}
.cache-time {
font-size: 11px;
color: #adb5bd;
margin-top: 6px;
}
.favorited-time {
font-size: 11px;
color: #ffc107;
margin-top: 6px;
font-weight: 500;
}
/* 操作列 */
.feed-actions {
display: flex;
gap: 8px;
min-width: 120px;
max-width: 150px;
flex-wrap: wrap;
}
.action-btn {
padding: 7px 14px;
border: none;
border-radius: 5px;
background: white;
color: #000000;
cursor: pointer;
font-size: 13px;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
transition: all 0.2s;
min-width: 60px;
font-weight: 500;
border: 1px solid #dee2e6;
box-sizing: border-box;
}
.action-btn:hover {
background: #f8f9fa;
text-decoration: none;
border-color: #ced4da;
transform: translateY(-1px);
color: #000000;
}
.action-btn.visit {
background: #467b96;
border-color: #467b96;
color: #fff;
}
.action-btn.visit:hover {
background: #3a6a83;
border-color: #3a6a83;
color: #fff;
}
.action-btn.copy {
background: #28a745;
border-color: #28a745;
color: #fff;
}
.action-btn.copy:hover {
background: #218838;
border-color: #218838;
color: #fff;
}
.action-btn.favorite-btn {
background: #ffc107;
border-color: #ffc107;
color: #FFF;
}
.action-btn.favorite-btn:hover {
background: #e0a800;
border-color: #e0a800;
color: #000;
}
.action-btn.favorite-btn.active {
background: #28a745;
border-color: #28a745;
color: #fff;
}
.action-btn.favorite-btn.active:hover {
background: #218838;
border-color: #218838;
color: #fff;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 70px 30px;
color: #6c757d;
background: white;
border-radius: 10px;
box-shadow: 0 3px 12px rgba(0,0,0,0.06);
margin-top: 30px;
border: 1px solid #e8ebf0;
}
.empty-state-icon {
font-size: 56px;
color: #dee2e6;
margin-bottom: 20px;
opacity: 0.7;
}
.empty-state-title {
font-size: 22px;
color: #2c3e50;
margin-bottom: 15px;
font-weight: 600;
}
.empty-state-message {
font-size: 15px;
color: #6c757d;
margin-bottom: 30px;
max-width: 500px;
margin-left: auto;
margin-right: auto;
line-height: 1.6;
}
.empty-state-actions {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.empty-state-btn {
padding: 11px 26px;
border: none;
border-radius: 6px;
background: #467b96;
color: #000000;
text-decoration: none;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.25s ease;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
box-shadow: 0 2px 6px rgba(70, 123, 150, 0.2);
}
.empty-state-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(70, 123, 150, 0.25);
text-decoration: none;
color: #000000;
background: #3a6a83;
}
.empty-state-btn.secondary {
background: white;
color: #467b96;
border: 1px solid #467b96;
box-shadow: 0 2px 4px rgba(70, 123, 150, 0.1);
}
.empty-state-btn.secondary:hover {
background: #f8f9fa;
color: #3a6a83;
border-color: #3a6a83;
}
/* 消息提示 */
.rss-alert {
padding: 16px 24px;
border-radius: 8px;
margin-bottom: 20px;
display: none;
position: fixed;
top: 30px;
left: 50%;
transform: translateX(-50%);
z-index: 99999;
max-width: 500px;
width: 90%;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.12);
animation: slideDown 0.3s ease;
border: 1px solid rgba(0,0,0,0.05);
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateX(-50%) translateY(-20px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
.rss-alert.success {
background: #d4edda;
border-color: #c3e6cb;
color: #000000;
}
.rss-alert.error {
background: #f8d7da;
border-color: #f5c6cb;
color: #000000;
}
.rss-alert.info {
background: #d1ecf1;
border-color: #bee5eb;
color: #000000;
}
.rss-alert.warning {
background: #fff3cd;
border-color: #ffeaa7;
color: #000000;
}
/* 加载动画 */
#loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.92);
display: none;
align-items: center;
justify-content: center;
z-index: 99999;
backdrop-filter: blur(3px);
}
.loading-content {
text-align: center;
background: white;
padding: 40px 50px;
border-radius: 10px;
box-shadow: 0 8px 30px rgba(0,0,0,0.15);
min-width: 320px;
border-top: 3px solid #467b96;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 3px solid #f3f3f3;
border-top: 3px solid #467b96;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
.loading-text {
color: #2c3e50;
font-size: 16px;
margin: 0;
font-weight: 500;
}
.loading-subtext {
color: #6c757d;
font-size: 13px;
margin-top: 8px;
}
/* 定时任务管理 */
.cron-management {
margin-top: 20px;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
border: 1px solid #e8ebf0;
}
.cron-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.cron-header h3 {
margin: 0;
font-size: 18px;
color: #2c3e50;
}
.cron-actions {
display: flex;
gap: 10px;
}
.cron-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.cron-info-box {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #467b96;
}
.cron-info-title {
font-size: 14px;
color: #495057;
margin-bottom: 8px;
font-weight: 500;
}
.cron-info-content {
font-size: 13px;
color: #6c757d;
}
.cron-url-box {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
font-family: monospace;
font-size: 12px;
word-break: break-all;
margin-bottom: 10px;
}
.cron-note {
font-size: 12px;
color: #6c757d;
margin-top: 10px;
}
/* 失败网址相关样式 */
.status-active {
color: #28a745;
font-weight: 500;
}
.status-inactive {
color: #6c757d;
text-decoration: line-through;
}
.failed-url-item {
background: #fff5f5;
border: 1px solid #f5c6cb;
border-radius: 6px;
padding: 12px;
margin-bottom: 10px;
}
.failed-url-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.failed-url-name {
font-weight: 500;
color: #2c3e50;
}
.failed-url-time {
font-size: 11px;
color: #6c757d;
}
.failed-url-error {
color: #dc3545;
font-size: 12px;
margin-bottom: 8px;
word-break: break-word;
}
.failed-url-actions {
display: flex;
gap: 8px;
}
/* 成功网址相关样式 */
.success-url-item {
background: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 6px;
padding: 12px;
margin-bottom: 10px;
}
.success-url-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.success-url-name {
font-weight: 500;
color: #155724;
}
.success-url-time {
font-size: 11px;
color: #6c757d;
}
.success-url-stats {
color: #28a745;
font-size: 12px;
margin-bottom: 8px;
word-break: break-word;
}
.success-url-actions {
display: flex;
gap: 8px;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.rss-header {
flex-direction: column;
align-items: flex-start;
gap: 18px;
}
.rss-stats {
font-size: 13px;
}
.empty-state {
padding: 50px 25px;
}
.empty-state-title {
font-size: 20px;
}
}
@media (max-width: 768px) {
.rss-feeds-container {
padding: 15px;
}
.rss-header {
padding: 18px 20px;
margin-bottom: 20px;
}
.rss-title {
font-size: 20px;
}
.rss-refresh-btn {
padding: 9px 20px;
font-size: 13px;
height: 38px;
}
.table-container {
margin-top: 15px;
border-radius: 8px;
}
.rss-feeds-table th,
.rss-feeds-table td {
padding: 14px 16px;
font-size: 13px;
}
.article-description {
-webkit-line-clamp: 2;
max-height: 45px;
}
.feed-actions {
flex-direction: column;
gap: 6px;
}
.action-btn {
width: 100%;
justify-content: center;
padding: 6px 12px;
font-size: 12px;
}
.empty-state {
padding: 40px 20px;
margin-top: 20px;
}
.empty-state-title {
font-size: 18px;
}
.empty-state-message {
font-size: 14px;
}
.empty-state-actions {
flex-direction: column;
align-items: center;
gap: 12px;
}
.empty-state-btn {
width: 220px;
justify-content: center;
padding: 10px 20px;
font-size: 14px;
}
.loading-content {
padding: 30px 40px;
min-width: 280px;
}
.loading-spinner {
width: 40px;
height: 40px;
}
.loading-text {
font-size: 15px;
}
/* 移动端下拉菜单优化 */
.status-filter-dropdown,
.batch-dropdown {
width: 100%;
}
.status-filter-btn,
.batch-dropdown-btn {
width: 100%;
justify-content: space-between;
}
.status-filter-dropdown-content,
.batch-dropdown-content {
width: 100%;
left: 0;
right: 0;
}
}
/* 美化滚动条 */
.table-container::-webkit-scrollbar {
height: 6px;
width: 6px;
}
.table-container::-webkit-scrollbar-track {
background: #f1f3f5;
border-radius: 3px;
}
.table-container::-webkit-scrollbar-thumb {
background: #adb5bd;
border-radius: 3px;
}
.table-container::-webkit-scrollbar-thumb:hover {
background: #868e96;
}
/* 工具类 */
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.text-muted {
color: #6c757d;
}
.text-primary {
color: #467b96;
}
.text-success {
color: #28a745;
}
/* 阅读全文相关样式 */
.read-full-btn {
padding: 4px 10px;
border: 1px solid #467b96;
background: white;
color: #467b96;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
max-width: 48px;
font-weight: 500;
margin-top2px;
transition: all 0.2s;
display: inline-block;
text-decoration: none;
}
.read-full-btn:hover {
background: #467b96;
color: white;
text-decoration: none;
}
/* 阅读全文弹窗样式 */
.full-content-body {
padding: 30px;
line-height: 1.8;
color: #333;
font-size: 15px;
overflow-y: auto;
flex: 1;
}
.full-content-body img {
max-width: 100%;
height: auto;
margin: 10px 0;
border-radius: 6px;
}
.full-content-body p {
margin-bottom: 15px;
}
.full-content-body h1,
.full-content-body h2,
.full-content-body h3,
.full-content-body h4 {
margin: 25px 0 15px 0;
color: #2c3e50;
font-weight: 600;
}
.full-content-body ul,
.full-content-body ol {
margin: 15px 0;
padding-left: 25px;
}
.full-content-body li {
margin-bottom: 8px;
}
.full-content-body code {
background: #f5f7fa;
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 13px;
color: #467b96;
}
.full-content-body pre {
background: #2c3e50;
color: #ecf0f1;
padding: 15px;
border-radius: 6px;
overflow-x: auto;
margin: 15px 0;
}
.full-content-body blockquote {
border-left: 4px solid #467b96;
padding-left: 15px;
margin: 15px 0;
color: #6c757d;
font-style: italic;
}
.full-content-body a {
color: #467b96;
text-decoration: none;
}
.full-content-body a:hover {
text-decoration: underline;
}
/* 文章元信息 */
.full-content-meta {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
margin-top: -20px;
background: #f8f9fa;
border-radius: 8px;
}
.meta-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #6c757d;
}
.meta-item i {
font-style: normal;
color: #467b96;
font-weight: 500;
}
/* 弹窗工具栏 */
.modal-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 25px;
border-top: 1px solid #e8ebf0;
background: #f8fafc;
}
.toolbar-actions {
display: flex;
gap: 10px;
}
/* 文章操作按钮 */
.article-action-btn {
padding: 8px 15px;
border: 1px solid #dee2e6;
background: white;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
color: #495057;
}
.article-action-btn:hover {
background: #f8f9fa;
border-color: #467b96;
color: #467b96;
}
.article-action-btn.primary {
background: #467b96;
border-color: #467b96;
color: white;
}
.article-action-btn.primary:hover {
background: #3a6a83;
border-color: #3a6a83;
color: white;
}
.article-action-btn.danger {
background: #dc3545;
border-color: #dc3545;
color: white;
}
.article-action-btn.danger:hover {
background: #c82333;
border-color: #c82333;
color: white;
}
/* 响应式调整 */
@media (max-width: 768px) {
.full-content-body {
padding: 20px;
font-size: 14px;
}
.full-content-meta {
flex-direction: column;
gap: 10px;
}
.modal-toolbar {
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.toolbar-actions {
width: 100%;
justify-content: space-between;
}
.article-action-btn {
padding: 6px 12px;
font-size: 12px;
}
}
</style>
<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 rss-feeds-container">
<!-- 页面头部 -->
<div class="rss-header">
<div>
<h2 class="rss-title">
<?php echo $showFavorites ? '收藏文章' : 'RSS信息订阅'; ?>
</h2>
<div class="rss-stats">
<?php if ($showFavorites): ?>
已藏 <strong><?php echo $favoriteStats['total']; ?></strong> 篇文章 |
显示 <?php echo count($feeds); ?> 条,第 <?php echo $currentPage; ?>/<?php echo $totalPages; ?> 页
<?php elseif (!empty($feeds)): ?>
已订阅 <strong><?php echo $totalFeeds; ?></strong> 条信息 |
显示 <?php echo count($feeds); ?> 条,第 <?php echo $currentPage; ?>/<?php echo $totalPages; ?> 页
<?php elseif ($rssUrlCount > 0): ?>
检测到 <strong><?php echo $rssUrlCount; ?></strong> 个RSS源
<?php if ($cacheStats['total'] > 0): ?>
| 缓存 <strong><?php echo $cacheStats['total']; ?></strong> 条记录
<?php endif; ?>
<?php else: ?>
暂无RSS源
<?php endif; ?>
</div>
</div>
<div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
<?php if ($rssUrlCount > 0): ?>
<button type="button" class="rss-refresh-btn" id="refresh-rss-btn">
<span class="refresh-text">手动刷新</span>
<span class="refreshing-spinner" style="display: none;"></span>
</button>
<button type="button" class="rss-refresh-btn clean-btn" id="clean-cache-btn" title="清理过期缓存">
清理缓存
</button>
<?php endif; ?>
<a href="<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>"
class="rss-refresh-btn manage-btn">
源头管理
</a>
<button type="button" class="rss-refresh-btn stats-btn" id="show-stats-btn" title="查看统计信息">
数据统计
</button>
</div>
</div>
<!-- 筛选栏 -->
<div class="rss-filter-bar">
<form method="get" action="" class="filter-form" style="display: contents;">
<input type="hidden" name="panel" value="UrlNav/Rss.php">
<input type="hidden" name="page" value="1">
<!-- 收藏按钮 -->
<div class="filter-group">
<a href="?panel=UrlNav/Rss.php&favorites=1&page=1<?php echo $search ? '&search=' . urlencode($search) : ''; ?>"
class="search-btn <?php echo $showFavorites ? 'active' : ''; ?>"
style="display: inline-flex; align-items: center; gap: 5px;">
<span></span>
已藏文章
</a>
</div>
<div class="filter-group">
<select name="category" class="filter-select" onchange="this.form.submit()" style="position: relative;">
<option value="all" <?php echo $categoryId === 'all' ? 'selected' : ''; ?>>全部分类</option>
<?php foreach ($categories as $cat): ?>
<option value="<?php echo $cat['id']; ?>" <?php echo $categoryId == $cat['id'] ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($cat['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- 星级筛选下拉菜单 -->
<div class="filter-group">
<select name="star" class="filter-select" onchange="this.form.submit()" style="position: relative;">
<?php foreach ($allStarRatings as $star): ?>
<option value="<?php echo $star['value']; ?>" <?php echo $starRating === $star['value'] ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($star['label']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- 状态筛选下拉菜单 -->
<div class="status-filter-dropdown" id="status-filter-dropdown">
<button type="button" class="status-filter-btn" onclick="toggleStatusFilter()">
全部状态
</button>
<div class="status-filter-dropdown-content">
<div class="status-filter-option" data-status="all" onclick="filterByStatus('all')">
全部 <span class="status-filter-count" id="count-all">0</span>
</div>
<div class="status-filter-option" data-status="unread" onclick="filterByStatus('unread')">
未读 <span class="status-filter-count" id="count-unread">0</span>
</div>
<div class="status-filter-option" data-status="read" onclick="filterByStatus('read')">
已读 <span class="status-filter-count" id="count-read">0</span>
</div>
<div class="status-filter-option" data-status="ignore" onclick="filterByStatus('ignore')">
无感 <span class="status-filter-count" id="count-ignore">0</span>
</div>
</div>
</div>
<!-- 批量操作下拉菜单 -->
<div class="batch-dropdown" id="batch-dropdown">
<button type="button" class="batch-dropdown-btn" onclick="toggleBatchDropdown()">
批量操作
</button>
<div class="batch-dropdown-content">
<div class="batch-option select-all" onclick="selectAllFeeds()">全部选择</div>
<div class="batch-option clear-selection" onclick="clearSelection()">取消选择</div>
<div class="batch-option mark-read" onclick="batchMarkAsRead()">标记已读</div>
<div class="batch-option mark-unread" onclick="batchMarkAsUnread()">标记未读</div>
<div class="batch-option mark-ignore" onclick="batchMarkAsIgnore()">标记无感</div>
<div class="batch-option export-status" onclick="exportStatusData()">导出状态</div>
<div class="batch-option import-status" onclick="importStatusData()">导入状态</div>
<div class="batch-option clear-status" onclick="clearAllStatuses()">清除状态</div>
</div>
</div>
<div class="filter-group">
<input type="text" name="search" class="search-input" placeholder="搜索文章标题或内容..." value="<?php echo htmlspecialchars($search); ?>">
</div>
<div class="filter-group">
<button type="submit" class="search-btn">立即搜索</button>
</div>
<?php if ($categoryId !== 'all' || $starRating !== 'all'|| $search || $showFavorites): ?>
<div class="filter-group">
<a href="?panel=UrlNav/Rss.php" class="clear-btn">清除筛选</a>
</div>
<?php endif; ?>
</form>
</div>
<!-- 内容区域 -->
<?php
if ($rssUrlCount == 0): ?>
<!-- 情况1没有任何RSS源 -->
<div class="empty-state">
<div class="empty-state-icon">📭</div>
<h3 class="empty-state-title">尚未添加RSS地址</h3>
<p class="empty-state-message">
您还没有为任何网址添加RSS地址。<br>
请在网址管理中为网站添加RSS地址然后返回此页面刷新RSS信息。
</p>
<div class="empty-state-actions">
<a href="<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>"
class="empty-state-btn">
去添加RSS地址
</a>
</div>
</div>
<?php elseif (empty($feeds)): ?>
<!-- 情况2有RSS源但没有文章 -->
<?php if ($showFavorites): ?>
<!-- 情况2.1:收藏模式下没有收藏文章 -->
<div class="empty-state">
<div class="empty-state-icon"></div>
<h3 class="empty-state-title">暂无收藏文章</h3>
<p class="empty-state-message">
您还没有收藏任何文章。<br>
在文章列表中找到喜欢的文章,点击"收藏"按钮即可添加到收藏夹。
</p>
<div class="empty-state-actions">
<a href="?panel=UrlNav/Rss.php" class="empty-state-btn">
查看所有文章
</a>
<a href="<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>"
class="empty-state-btn secondary">
管理RSS地址
</a>
</div>
</div>
<?php else: ?>
<!-- 情况2.2:普通模式下没有文章 -->
<div class="empty-state">
<div class="empty-state-icon">🔄</div>
<h3 class="empty-state-title">RSS缓存为空</h3>
<p class="empty-state-message">
<?php if ($categoryId !== 'all'): ?>
在选定的分类中未找到RSS缓存记录。<br>
请点击"手动刷新RSS"按钮获取最新文章,或选择其他分类。
<?php elseif ($starRating !== 'all'): ?>
"<?php
// 使用一致的显示名称
$starDisplayNames = [
'0' => '无星级',
'starred' => '有星级',
'1' => '一星',
'2' => '二星',
'3' => '三星'
];
// 确保 $starRating 是后端能处理的值
$filterValue = $starRating;
if ($starRating === 'none') {
$filterValue = '0'; // 将 'none' 转换为 '0'
} elseif ($starRating === 'has') {
$filterValue = 'starred'; // 将 'has' 转换为 'starred'
}
$starLabel = isset($starDisplayNames[$filterValue]) ? $starDisplayNames[$filterValue] : '所有';
echo htmlspecialchars($starLabel);
?>"中未找到RSS缓存记录。<br>
请点击"手动刷新RSS"按钮获取最新文章,或选择其他星级。
<?php elseif ($search): ?>
未找到包含"<?php echo htmlspecialchars($search); ?>"的文章。<br>
请尝试其他关键词,或点击"清除筛选"按钮查看所有文章。
<?php else: ?>
检测到 <strong><?php echo $rssUrlCount; ?></strong> 个RSS源但缓存为空。<br>
请点击"手动刷新RSS"按钮获取最新的RSS文章。
<?php endif; ?>
</p>
<div class="empty-state-actions">
<button type="button" class="empty-state-btn" id="prompt-refresh-btn">
手动刷新RSS
</button>
<a href="<?php echo \Typecho\Common::url('extending.php?panel=UrlNav/Manage.php', $options->adminUrl); ?>"
class="empty-state-btn secondary">
管理RSS地址
</a>
<?php if ($categoryId !== 'all' || $search): ?>
<a href="?panel=UrlNav/Rss.php" class="empty-state-btn secondary">
清除筛选
</a>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php else: ?>
<!-- 情况3正常显示表格 -->
<div class="table-container">
<table class="rss-feeds-table">
<colgroup>
<col/>
<col/>
<col/>
<col/>
<col/>
</colgroup>
<thead>
<tr>
<th style="width: 30px;">状态</th>
<!--<th style="width: 20px;">序号</th>-->
<th>网站</th>
<th>文章</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php
$index = ($currentPage - 1) * $pageSize + 1;
foreach ($feeds as $feed):
// 转换为北京时间
if ($showFavorites) {
$beijingPubDate = formatBeijingDateTime($feed['pub_date'], 'Y-m-d H:i');
$beijingCachedAt = '';
$timeAgo = '';
} else {
$beijingPubDate = formatBeijingDateTime($feed['pub_date'], 'Y-m-d H:i');
$beijingCachedAt = !empty($feed['cached_at']) ? formatSimpleTime($feed['cached_at']) : '';
$timeAgo = formatTimeAgo($feed['pub_date']);
}
$feedId = md5(($feed['feed_link'] ?? '') . ($feed['feed_title'] ?? '') . ($feed['pub_date'] ?? ''));
// 检查是否有完整内容
$hasFullContent = hasFullContent($feed);
$articleExcerpt = getArticleExcerpt($feed['feed_description'] ?? '', 120);
// 获取收藏状态
$isFavorite = UrlNav_Plugin::isFavorite($feed['id'], 0);
?>
<tr data-feed-id="<?php echo $feedId; ?>">
<!-- 状态列 -->
<td class="feed-status-cell">
<div class="status-selector" data-feed-id="<?php echo $feedId; ?>" onclick="toggleStatusDropdown(this)">
<div class="status-dot status-unread" title="未读"></div>
<div class="status-dropdown">
<div class="status-option" title="未读" data-status="unread" data-color="#dc3545" onclick="setFeedStatus('<?php echo $feedId; ?>', 'unread')"></div>
<div class="status-option" title="已读" data-status="read" data-color="#28a745" onclick="setFeedStatus('<?php echo $feedId; ?>', 'read')"></div>
<div class="status-option" title="无感" data-status="ignore" data-color="#6c757d" onclick="setFeedStatus('<?php echo $feedId; ?>', 'ignore')"></div>
</div>
</div>
</td>
<!-- <td class="feed-index"><?php echo $index++; ?></td>-->
<td>
<div class="site-info">
<div class="site-title"> <a href="?panel=UrlNav/Rss.php&search=<?php echo urlencode($feed['site_title'] ?? ''); ?>"
title="查看此网站的所有文章"
style="color: #2c3e50; text-decoration: none; font-weight: 600; cursor: pointer;">
<?php echo htmlspecialchars($feed['site_title'] ?? '未知网站'); ?>
</a>
</div>
<div class="star-rating-section" style="display: flex; align-items: center; gap: 5px; margin-top: 4px;">
<?php
// 确保安全获取并转换为整数
$feedStarRating = isset($feed['star_rating']) ? intval($feed['star_rating']) : 0;
if ($feedStarRating > 0):
$stars = '';
for ($i = 1; $i <= 3; $i++) {
$stars .= ($i <= $feedStarRating) ? '★' : '☆';
}
?>
<a href="?panel=UrlNav/Rss.php&star=<?php echo $feedStarRating; ?>&page=1"
class="star-link"
title="查看此星级的所有文章"
style="text-decoration: none; color: #ffc107; font-size: 14px; cursor: pointer;">
<?php echo $stars; ?>
</a>
<!--<span style="font-size: 11px; color: #ff9800; margin-left: 2px;">
(<?php echo $feedStarRating; ?>星)
</span>-->
<?php else: ?>
<a href="?panel=UrlNav/Rss.php&star=0&page=1"
class="no-star-link"
title="查看无星级的文章"
style="text-decoration: none; color: #6c757d; font-size: 12px; cursor: pointer;">
☆☆☆
</a>
<?php endif; ?>
</div>
<div class="site-url">
<a href="<?php echo htmlspecialchars($feed['site_url'] ?? '#'); ?>" target="_blank" title="访问网站">
<?php echo htmlspecialchars($feed['site_url'] ?? '未知地址'); ?>
</a>
</div>
<?php if (!empty($feed['category_name'])): ?>
<span class="category-tag"><?php echo htmlspecialchars($feed['category_name']); ?></span>
<?php endif; ?>
</div>
</td>
<td>
<div class="article-info">
<div class="article-title">
<a href="<?php echo htmlspecialchars($feed['feed_link'] ?? '#'); ?>" target="_blank" title="查看文章">
<?php echo htmlspecialchars($feed['feed_title'] ?? '无标题'); ?>
</a>
</div>
<?php if (!empty($articleExcerpt)): ?>
<div class="article-description">
<?php echo htmlspecialchars($articleExcerpt); ?>
</div>
<?php endif; ?>
<!-- 阅读全文按钮 -->
<?php if ($hasFullContent): ?>
<a href="javascript:void(0);"
class="read-full-btn"
onclick="showFullContent(<?php echo htmlspecialchars(json_encode([
'id' => $feed['id'],
'title' => $feed['feed_title'] ?? '无标题',
'site_title' => $feed['site_title'] ?? '未知网站',
'site_url' => $feed['site_url'] ?? '',
'link' => $feed['feed_link'] ?? '',
'pub_date' => $beijingPubDate,
'category' => $feed['category_name'] ?? '',
'full_content' => $feed['full_content'],
'description' => $feed['feed_description'] ?? ''
])); ?>)">
阅读全文
</a>
<?php endif; ?>
</div>
</td>
<td>
<div class="time-info">
<div class="pub-date" title="北京时间:<?php echo $beijingPubDate; ?>">
<?php echo $beijingPubDate ?: '未知时间'; ?>
</div>
<?php if (!$showFavorites && $timeAgo): ?>
<div class="time-ago"><?php echo $timeAgo; ?></div>
<?php endif; ?>
<?php if (!$showFavorites && $beijingCachedAt): ?>
<div class="cache-time" title="缓存时间(北京时间)">
<?php echo $beijingCachedAt; ?>
</div>
<?php endif; ?>
<?php if ($showFavorites && !empty($feed['favorited_at'])): ?>
<div class="favorited-time" title="收藏时间(北京时间)">
<?php echo formatSimpleTime($feed['favorited_at']); ?>
</div>
<?php endif; ?>
</div>
</td>
<td>
<div class="feed-actions">
<a href="<?php echo htmlspecialchars($feed['feed_link'] ?? '#'); ?>" target="_blank"
class="action-btn visit" title="查看文章">
原文
</a>
<button type="button" class="action-btn copy"
onclick="copyToClipboard('<?php echo htmlspecialchars(addslashes($feed['feed_link'] ?? '')); ?>')"
title="复制链接">
复制
</button>
<!-- 收藏按钮 -->
<?php if (!$showFavorites): ?>
<button type="button" class="action-btn favorite-btn <?php echo $isFavorite ? 'active' : ''; ?>"
data-feed-id="<?php echo $feed['id']; ?>"
data-is-favorite="<?php echo $isFavorite ? '1' : '0'; ?>"
onclick="toggleFavorite(this, <?php echo $feed['id']; ?>, <?php echo $isFavorite ? 'true' : 'false'; ?>)"
title="<?php echo $isFavorite ? '已藏,点击取消' : '收藏文章'; ?>">
<?php echo $isFavorite ? '已藏' : '收藏'; ?>
</button>
<?php else: ?>
<!-- 在收藏页面显示取消收藏按钮 -->
<button type="button" class="action-btn favorite-btn active"
data-feed-id="<?php echo $feed['original_feed_id'] ?? $feed['id']; ?>"
data-is-favorite="1"
onclick="toggleFavorite(this, <?php echo $feed['original_feed_id'] ?? $feed['id']; ?>, true)"
title="已藏,点击取消">
取消收藏
</button>
<?php endif; ?>
<!-- 找到操作按钮部分,在收藏按钮后面添加保存按钮 -->
<?php if (hasFullContent($feed)): ?>
<button type="button" class="action-btn save-btn"
onclick="saveAsMarkdown(<?php echo htmlspecialchars(json_encode([
'id' => $feed['id'],
'title' => $feed['feed_title'] ?? '无标题',
'site_title' => $feed['site_title'] ?? '未知网站',
'site_url' => $feed['site_url'] ?? '',
'link' => $feed['feed_link'] ?? '',
'pub_date' => $beijingPubDate,
'category' => $feed['category_name'] ?? '',
'full_content' => $feed['full_content'],
'description' => $feed['feed_description'] ?? ''
])); ?>)"
title="保存全文为Markdown格式">
保存
</button>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- 分页 -->
<?php if ($totalPages > 1): ?>
<div class="rss-pagination">
<?php
// 构建基础URL
$baseUrl = '?panel=UrlNav/Rss.php';
if ($showFavorites) {
$baseUrl .= '&favorites=1';
}
if ($categoryId !== 'all') {
$baseUrl .= '&category=' . urlencode($categoryId);
}
if ($starRating !== 'all') {
$baseUrl .= '&star=' . urlencode($starRating);
} if ($search) {
$baseUrl .= '&search=' . urlencode($search);
}
?>
<?php if ($currentPage > 1): ?>
<a href="<?php echo $baseUrl; ?>&page=1" class="pagination-btn">首页</a>
<a href="<?php echo $baseUrl; ?>&page=<?php echo $currentPage - 1; ?>" class="pagination-btn">上一页</a>
<?php else: ?>
<span class="pagination-btn" disabled>首页</span>
<span class="pagination-btn" disabled>上一页</span>
<?php endif; ?>
<?php
// 显示分页数字
$startPage = max(1, $currentPage - 2);
$endPage = min($totalPages, $currentPage + 2);
if ($startPage > 1): ?>
<span class="pagination-ellipsis">...</span>
<?php endif;
for ($i = $startPage; $i <= $endPage; $i++): ?>
<?php if ($i == $currentPage): ?>
<span class="pagination-current"><?php echo $i; ?></span>
<?php else: ?>
<a href="<?php echo $baseUrl; ?>&page=<?php echo $i; ?>" class="pagination-btn"><?php echo $i; ?></a>
<?php endif; ?>
<?php endfor;
if ($endPage < $totalPages): ?>
<span class="pagination-ellipsis">...</span>
<?php endif; ?>
<?php if ($currentPage < $totalPages): ?>
<a href="<?php echo $baseUrl; ?>&page=<?php echo $currentPage + 1; ?>" class="pagination-btn">下一页</a>
<a href="<?php echo $baseUrl; ?>&page=<?php echo $totalPages; ?>" class="pagination-btn">末页</a>
<?php else: ?>
<span class="pagination-btn" disabled>下一页</span>
<span class="pagination-btn" disabled>末页</span>
<?php endif; ?>
<div class="pagination-info">
<?php echo $currentPage; ?> 页 / 共 <?php echo $totalPages; ?> 页,每页 <?php echo $pageSize; ?> 条
</div>
</div>
<?php endif; ?>
<?php endif; ?>
<!-- RSS定时任务管理 -->
<div class="cron-management">
<div style="display: flex; justify-content: center; align-items: center; margin-bottom: 20px;">
<h3 style="margin: 0;color:#000;text-align:center;">宝塔任务配置、统计</h3>
</div>
<div style="">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;">
<div>
<h4 style="margin-top: 0; margin-bottom: 10px; color: #2c3e50;">RSS任务地址</h4>
<div style="background: #f8f9fa; color:#000;padding: 15px; border-radius: 6px; font-family: monospace; font-size: 12px; word-break: break-all; margin-bottom: 10px; position: relative;">
<?php
$rssCronUrl = getRssCronUrl();
echo htmlspecialchars($rssCronUrl);
?><br>
<button type="button" class="action-btn copy"
style="padding: 3px 8px; font-size: 11px;margin-top:10px;"
onclick="copyToClipboard('<?php echo htmlspecialchars($rssCronUrl); ?>')"
title="复制RSS任务地址">
复制
</button>
</div>
<div style="font-size: 12px; color: #6c757d;">
请妥善保管此URL不要泄露给他人
</div>
</div>
</div>
<!--<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e8ebf0;">
<h4 style="margin-top: 0; margin-bottom: 15px; color: #2c3e50;">宝塔计划任务设置步骤</h4>
<div style="background: #f8f9fa; padding: 15px; border-radius: 6px; margin-bottom: 15px;">
<div style="display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px;">
<span style="background: #467b96; color: #000000; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; flex-shrink: 0;">1</span>
<div>
<strong style="color:#000;">登录宝塔面板</strong>
<div style="font-size: 12px; color: #6c757d;">进入"计划任务"功能</div>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px;">
<span style="background: #467b96; color: #000000; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; flex-shrink: 0;">2</span>
<div>
<strong style="color:#000;">添加Shell脚本任务</strong>
<div style="font-size: 12px; color: #6c757d;">任务类型选择"Shell脚本"</div>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px;">
<span style="background: #467b96; color: #000000; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; flex-shrink: 0;">3</span>
<div>
<strong style="color:#000;">设置执行周期</strong>
<div style="font-size: 12px; color: #6c757d;">建议每小时或每30分钟执行一次</div>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 10px;">
<span style="background: #467b96; color: #000000; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; flex-shrink: 0;">4</span>
<div>
<strong style="color:#000;">填写脚本内容</strong>
<div style="font-size: 12px; color: #6c757d;">复制下面的Shell命令到脚本框中</div>
</div>
</div>
</div>-->
<!-- <div style="background: #f8f9fa; padding: 15px; border-radius: 6px; margin-bottom: 15px;">
<h5 style="margin-top: 0; margin-bottom: 10px; color: #2c3e50;">Shell脚本命令</h5>
<div style="background: #2c3e50; color: #e9ecef; padding: 10px; border-radius: 4px; font-family: monospace; font-size: 12px; word-break: break-all;">
curl -s "<?php echo htmlspecialchars($rssCronUrl); ?>"
</div>
<div style="font-size: 11px; color: #6c757d; margin-top: 5px;">
如果服务器没有安装curl可以使用wget -O /dev/null -q "<?php echo htmlspecialchars($rssCronUrl); ?>"
</div>
</div>-->
<!--<div style="background: #e7f3ff; padding: 12px; border-radius: 6px; border-left: 4px solid #467b96;">
<div style="display: flex; align-items: flex-start; gap: 8px;">
<div>
<strong style="color:#000;">提示</strong>
<div style="font-size: 12px; color: #2c3e50; margin-top: 2px;">
配置后系统将按宝塔设置的时间周期自动刷新RSS信息无需人工干预。
建议设置为每1-2小时执行一次,避免对目标网站造成过大压力。
</div>
</div>
</div>
</div>-->
<!-- RSS定时任务监控部分 -->
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e8ebf0;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h4 style="margin: 0; color: #2c3e50;">定时任务监控</h4>
<div style="display: flex; gap: 10px;">
<button type="button" class="action-btn" onclick="showRssCronStats()">
统计
</button>
<button type="button" class="action-btn" onclick="showRssCronLogs()">
日志
</button>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px;">
<div style="background: #f8f9fa; padding: 12px; border-radius: 6px; text-align: center;">
<div style="font-size: 11px; color: #6c757d; margin-bottom: 4px;">总执行次数</div>
<div id="rss-cron-total" style="font-size: 18px; font-weight: bold; color: #467b96;"><?php echo $rssCronStats['total'] ?? 0; ?></div>
</div>
<div style="background: #f8f9fa; padding: 12px; border-radius: 6px; text-align: center;">
<div style="font-size: 11px; color: #6c757d; margin-bottom: 4px;">成功率</div>
<div id="rss-cron-success-rate" style="font-size: 18px; font-weight: bold; color: #28a745;"><?php echo $rssCronStats['success_rate'] ?? 0; ?>%</div>
</div>
<div style="background: #f8f9fa; padding: 12px; border-radius: 6px; text-align: center;">
<div style="font-size: 11px; color: #6c757d; margin-bottom: 4px;">最后执行</div>
<div id="rss-cron-last-executed" style="font-size: 14px; color: #6c757d;">
<?php
if (!empty($rssCronStats['last_executed'])) {
echo formatStatsTime($rssCronStats['last_executed']);
} else {
echo '从未执行';
}
?>
</div>
</div>
<!--<div style="background: #f8f9fa; padding: 12px; border-radius: 6px; text-align: center;">
<div style="font-size: 11px; color: #6c757d; margin-bottom: 4px;">当前状态</div>
<div id="rss-cron-status" style="font-size: 18px; font-weight: bold; <?php echo $isRssLocked ? 'color: #dc3545;' : 'color: #28a745;'; ?>">
<?php echo $isRssLocked ? '运行中' : '空闲'; ?>
</div>
</div>
</div>-->
<?php if ($isRssLocked): ?>
<div style="margin-top: 15px; padding: 12px; background: <?php echo $rssLockDuration > 600 ? '#f8d7da' : '#fff3cd'; ?>; border-radius: 6px; border: 1px solid <?php echo $rssLockDuration > 600 ? '#f5c6cb' : '#ffeaa7'; ?>;">
<div style="display: flex; align-items: center; gap: 10px;">
<div style="flex: 1; font-size: 13px; color: <?php echo $rssLockDuration > 600 ? '#721c24' : '#856404'; ?>;">
<strong>RSS定时任务状态</strong>
已运行 <?php echo floor($rssLockDuration / 60); ?> 分 <?php echo $rssLockDuration % 60; ?> 秒
<?php if ($rssLockDuration > 600): ?>
<br><small>如果确定没有任务在运行,请等待或重启相关服务。</small>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 消息提示 -->
<div class="rss-alert" id="rss-message-alert" style="display: none;"></div>
<!-- 统计信息弹窗 -->
<div id="stats-modal" class="modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10000; align-items: center; justify-content: center;">
<div class="modal-content" style="background: white; padding: 30px; border-radius: 10px; max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto;">
<h3 style="margin-top: 0;" id="modal-title">统计信息</h3>
<div id="modal-content">
<p>正在加载统计信息...</p>
</div>
<!--<div style="display: flex; justify-content: flex-end; margin-top: 20px; gap: 10px;">
<button type="button" class="action-btn" onclick="closeModal()">关闭</button>
</div>-->
</div>
</div>
<!-- 阅读全文弹窗 -->
<div id="full-content-modal" class="modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100000; align-items: center; justify-content: center; padding: 20px; backdrop-filter: blur(5px);">
<div class="modal-content" style="background: white; border-radius: 12px; max-width: 900px; width: 100%; max-height: 85vh; display: flex; flex-direction: column; box-shadow: 0 25px 100px rgba(0,0,0,0.4); overflow: hidden; ">
<div style="display: flex; justify-content: space-between; align-items: center; padding: 20px 25px; background: linear-gradient(135deg, #467b96 0%, #3a6a83 100%); border-bottom: 1px solid rgba(0,0,0,0.1);">
<h3 style="margin: 0 auto; font-size: 18px; font-weight: 600; color: white; display: flex; align-items: center; gap: 10px;justify-content:center;">
<span id="full-content-title" style="flex: 1;"><!--文章详情--></span>
</h3>
<button type="button" onclick="closeFullContentModal()" style="background: rgba(255,255,255,0.2); border: none; color: white; width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 18px; transition: all 0.2s; font-weight: bold;">&times;</button>
</div>
<div style="flex: 1; overflow-y: auto; padding: 0;">
<!-- 文章头部信息 -->
<div style="padding: 25px; border-bottom: 1px solid #e8ebf0; background: #f8fafc;">
<div style="display: flex; align-items: flex-start; gap: 15px; margin-bottom: 15px;">
<div style="flex: 1;">
<div id="full-content-site-title" style="font-size: 15px; color: #467b96; font-weight: 500; margin-bottom: 5px;"></div>
<div id="full-content-original-title" style="font-size: 20px; font-weight: 600; color: #2c3e50; line-height: 1.4; margin-bottom: 10px;"></div>
</div>
</div>
<!-- 文章元信息 -->
<div id="full-content-meta" class="full-content-meta">
<!-- 元信息将通过JavaScript动态填充 -->
</div>
</div>
<!-- 文章内容区域 -->
<div id="full-content-body" class="full-content-body">
<!-- 内容将通过JavaScript动态填充 -->
</div>
</div>
<!-- 工具栏 -->
<div class="modal-toolbar">
<div id="full-content-status" style="font-size: 13px; color: #6c757d;">
加载中...
</div>
<div class="toolbar-actions">
<button type="button" class="article-action-btn" onclick="copyFullContentLink()">
复制
</button>
<button type="button" class="article-action-btn primary" onclick="openOriginalLink()">
原文
</button>
<!-- 新增保存为Markdown按钮 -->
<button type="button" class="article-action-btn" onclick="saveCurrentArticle()">
保存
</button>
<button type="button" class="article-action-btn" onclick="closeFullContentModal()">
关闭
</button>
</div>
</div>
</div>
</div>
<!-- 加载动画 -->
<div id="loading-overlay">
<div class="loading-content">
<div class="loading-spinner"></div>
<p class="loading-text">正在刷新RSS信息请稍候...</p>
<p class="loading-subtext">这可能需要一些时间,请耐心等待</p>
</div>
</div>
<script>
// 保存当前阅读的文章
function saveCurrentArticle() {
if (window.currentArticleData) {
saveAsMarkdown(window.currentArticleData);
}
}
// HTML转Markdown的简单函数
function htmlToMarkdown(html) {
if (!html) return '';
let markdown = html;
// 基本转换
markdown = markdown.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n\n');
markdown = markdown.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n\n');
markdown = markdown.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n\n');
markdown = markdown.replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n');
markdown = markdown.replace(/<br\s*\/?>/gi, ' \n');
markdown = markdown.replace(/<strong[^>]*>(.*?)<\/strong>/gi, '**$1**');
markdown = markdown.replace(/<b[^>]*>(.*?)<\/b>/gi, '**$1**');
markdown = markdown.replace(/<em[^>]*>(.*?)<\/em>/gi, '*$1*');
markdown = markdown.replace(/<i[^>]*>(.*?)<\/i>/gi, '*$1*');
markdown = markdown.replace(/<a[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi, '[$2]($1)');
markdown = markdown.replace(/<img[^>]*src=["']([^"']+)["'][^>]*alt=["']([^"']*)["'][^>]*>/gi, '![$2]($1)');
markdown = markdown.replace(/<li[^>]*>(.*?)<\/li>/gi, '- $1\n');
markdown = markdown.replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gi, '> $1\n\n');
markdown = markdown.replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`');
// 移除所有HTML标签
markdown = markdown.replace(/<[^>]+>/g, '');
// 处理特殊字符
markdown = markdown.replace(/&nbsp;/g, ' ');
markdown = markdown.replace(/&amp;/g, '&');
markdown = markdown.replace(/&lt;/g, '<');
markdown = markdown.replace(/&gt;/g, '>');
markdown = markdown.replace(/&quot;/g, '"');
return markdown.trim();
}
// 保存为Markdown文件
function saveAsMarkdown(articleData) {
if (!articleData || !articleData.full_content) {
showRssMessage('没有完整的文章内容可供保存', 'warning');
return;
}
try {
// 生成Markdown内容
const title = articleData.title || '无标题';
const site = articleData.site_title || '未知网站';
const date = articleData.pub_date || new Date().toISOString().slice(0, 10);
const link = articleData.link || '';
let markdown = `# ${title}\n\n`;
markdown += `**来源:** ${site}\n`;
markdown += `**时间:** ${date}\n`;
if (link) markdown += `**链接:** ${link}\n`;
markdown += '\n---\n\n';
// 转换正文内容
const content = htmlToMarkdown(articleData.full_content);
markdown += content;
// 生成文件名
const cleanTitle = title.replace(/[\\/*?:"<>|]/g, '_').substring(0, 50);
const cleanSite = site.replace(/[\\/*?:"<>|]/g, '_').substring(0, 20);
const fileName = `${date.replace(/[^0-9]/g, '')}_${cleanSite}_${cleanTitle}.md`;
// 创建并下载文件
const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const linkElem = document.createElement('a');
linkElem.href = url;
linkElem.download = fileName;
linkElem.style.display = 'none';
document.body.appendChild(linkElem);
linkElem.click();
document.body.removeChild(linkElem);
URL.revokeObjectURL(url);
showRssMessage('已保存', 'success');
} catch (error) {
console.error('保存失败:', error);
showRssMessage('保存失败: ' + error.message, 'error');
}
}
// 页面加载后自动加载定时任务统计
document.addEventListener('DOMContentLoaded', function() {
// 加载RSS定时任务统计信息
loadRssCronStats();
// 绑定所有刷新按钮
bindRefreshButton('refresh-rss-btn');
bindRefreshButton('prompt-refresh-btn');
// 绑定清理缓存按钮
var cleanBtn = document.getElementById('clean-cache-btn');
if (cleanBtn) {
cleanBtn.addEventListener('click', function(e) {
e.preventDefault();
cleanRssCache();
});
}
// 绑定统计按钮
var statsBtn = document.getElementById('show-stats-btn');
if (statsBtn) {
statsBtn.addEventListener('click', function(e) {
e.preventDefault();
showStats();
});
}
// 绑定RSS定时任务监控按钮
var cronStatsBtn = document.querySelector('.cron-management .action-btn[onclick*="showRssCronStats"]');
if (cronStatsBtn) {
cronStatsBtn.onclick = function(e) {
e.preventDefault();
showRssCronStats();
};
}
var cronLogsBtn = document.querySelector('.cron-management .action-btn[onclick*="showRssCronLogs"]');
if (cronLogsBtn) {
cronLogsBtn.onclick = function(e) {
e.preventDefault();
showRssCronLogs();
};
}
// 复制功能
window.copyToClipboard = function(text) {
if (!text || text === '#') {
showRssMessage('链接无效,无法复制', 'warning');
return;
}
navigator.clipboard.writeText(text).then(function() {
showRssMessage('链接已复制到剪贴板', 'success');
}, function() {
// 降级方案
var textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
try {
var successful = document.execCommand('copy');
if (successful) {
showRssMessage('链接已复制到剪贴板', 'success');
}
} catch (err) {
showRssMessage('复制失败:' + err, 'error');
}
document.body.removeChild(textarea);
});
};
// 自动隐藏消息
setTimeout(function() {
var alert = document.getElementById('rss-message-alert');
if (alert && alert.style.display === 'block') {
alert.style.display = 'none';
}
}, 5000);
// 添加回车键刷新支持
document.addEventListener('keydown', function(e) {
if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) {
e.preventDefault();
var refreshBtn = document.getElementById('refresh-rss-btn') || document.getElementById('prompt-refresh-btn');
if (refreshBtn && !refreshBtn.disabled) {
refreshBtn.click();
}
}
});
// 关闭模态框
document.addEventListener('click', function(e) {
var modal = document.getElementById('stats-modal');
if (e.target === modal) {
closeModal();
}
var fullContentModal = document.getElementById('full-content-modal');
if (e.target === fullContentModal) {
closeFullContentModal();
}
// 点击页面其他地方关闭下拉菜单
if (!e.target.closest('.status-filter-dropdown')) {
document.querySelectorAll('.status-filter-dropdown').forEach(dropdown => {
dropdown.classList.remove('active');
});
}
if (!e.target.closest('.batch-dropdown')) {
document.querySelectorAll('.batch-dropdown').forEach(dropdown => {
dropdown.classList.remove('active');
});
}
if (!e.target.closest('.status-selector')) {
document.querySelectorAll('.status-selector').forEach(selector => {
selector.classList.remove('active');
});
}
});
// ESC键关闭弹窗
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeModal();
closeFullContentModal();
// 关闭所有下拉菜单
document.querySelectorAll('.status-filter-dropdown, .batch-dropdown, .status-selector').forEach(el => {
el.classList.remove('active');
});
}
});
// 初始化状态管理
if (document.querySelector('.rss-feeds-table')) {
initStatusManagement();
}
});
// 设置插件配置
window.UrlNavConfig = {
actionUrl: '<?php echo Typecho_Common::url('/action/urlnav', $options->index); ?>',
adminUrl: '<?php echo $options->adminUrl; ?>'
};
// 显示加载动画
function showLoading() {
document.getElementById('loading-overlay').style.display = 'flex';
}
// 隐藏加载动画
function hideLoading() {
document.getElementById('loading-overlay').style.display = 'none';
}
// 显示消息
function showRssMessage(message, type) {
var alert = document.getElementById('rss-message-alert');
if (!alert) {
alert = document.createElement('div');
alert.id = 'rss-message-alert';
alert.className = 'rss-alert';
document.body.appendChild(alert);
}
alert.className = 'rss-alert ' + type;
alert.innerHTML = message;
alert.style.display = 'block';
setTimeout(function() {
alert.style.display = 'none';
}, 5000);
}
// 加载RSS定时任务统计
function loadRssCronStats() {
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=getRssCronStats',
credentials: 'include'
})
.then(response => response.json())
.then(data => {
if (data.success) {
var stats = data.data;
var totalElement = document.getElementById('rss-cron-total');
var successRateElement = document.getElementById('rss-cron-success-rate');
var lastExecutedElement = document.getElementById('rss-cron-last-executed');
var statusElement = document.getElementById('rss-cron-status');
if (totalElement) totalElement.textContent = stats.total || 0;
if (successRateElement) successRateElement.textContent = stats.success_rate || '0%';
if (lastExecutedElement) {
if (stats.last_executed) {
lastExecutedElement.textContent = formatBeijingDateJS(stats.last_executed);
} else {
lastExecutedElement.textContent = '从未执行';
}
}
}
})
.catch(error => {
console.error('加载RSS定时任务统计失败:', error);
});
}
// 手动刷新RSS
function refreshRss() {
showLoading();
// 更新按钮状态
var refreshBtn = document.getElementById('refresh-rss-btn');
var promptBtn = document.getElementById('prompt-refresh-btn');
if (refreshBtn) {
refreshBtn.disabled = true;
var spinner = refreshBtn.querySelector('.refreshing-spinner');
var text = refreshBtn.querySelector('.refresh-text');
if (spinner) spinner.style.display = 'inline-block';
if (text) text.textContent = '刷新中...';
}
if (promptBtn) {
promptBtn.disabled = true;
promptBtn.textContent = '刷新中...';
}
// 发送请求
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=getRssFeeds',
credentials: 'include'
})
.then(response => {
if (!response.ok) {
if (response.status === 403) {
throw new Error('无权限访问,请重新登录');
}
throw new Error('HTTP错误: ' + response.status);
}
return response.json();
})
.then(data => {
hideLoading();
// 恢复按钮状态
if (refreshBtn) {
refreshBtn.disabled = false;
var spinner = refreshBtn.querySelector('.refreshing-spinner');
var text = refreshBtn.querySelector('.refresh-text');
if (spinner) spinner.style.display = 'none';
if (text) text.textContent = '手动刷新RSS';
}
if (promptBtn) {
promptBtn.disabled = false;
promptBtn.textContent = '手动刷新RSS';
}
if (data.success) {
var message = data.message;
if (data.newArticles !== undefined) {
message += ',新增 ' + data.newArticles + ' 篇文章';
}
showRssMessage(message, 'success');
// 3秒后刷新页面
setTimeout(() => {
window.location.reload();
}, 3000);
} else {
showRssMessage(data.message, 'error');
}
})
.catch(error => {
hideLoading();
// 恢复按钮状态
if (refreshBtn) {
refreshBtn.disabled = false;
var spinner = refreshBtn.querySelector('.refreshing-spinner');
var text = refreshBtn.querySelector('.refresh-text');
if (spinner) spinner.style.display = 'none';
if (text) text.textContent = '手动刷新RSS';
}
if (promptBtn) {
promptBtn.disabled = false;
promptBtn.textContent = '手动刷新RSS';
}
console.error('刷新错误:', error);
if (error.message.includes('无权限') || error.message.includes('403') || error.message.includes('401')) {
showRssMessage('权限错误:请确保您已登录管理员账号', 'warning');
setTimeout(() => {
window.location.href = window.UrlNavConfig.adminUrl + 'login.php';
}, 2000);
} else {
showRssMessage('刷新失败:' + error.message, 'error');
}
});
}
// 清理缓存函数
function cleanRssCache() {
if (!confirm('确定要清理RSS缓存吗\n\n这将删除所有陈旧的缓存记录但不会影响RSS源本身。')) {
return;
}
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=cleanRssCache',
credentials: 'include'
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
var message = '缓存清理完成';
if (data.cleaned_count > 0) {
message += ',删除了 ' + data.cleaned_count + ' 条记录';
} else {
message += ',没有需要清理的记录';
}
showRssMessage(message, 'success');
// 2秒后刷新页面
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
showRssMessage('清理失败:' + data.message, 'error');
}
})
.catch(error => {
hideLoading();
showRssMessage('清理失败:' + error.message, 'error');
});
}
// 切换收藏状态函数 - 修复版
function toggleFavorite(button, feedId, isFavorite) {
// 修复直接从按钮的data属性获取当前状态
if (typeof isFavorite === 'undefined') {
isFavorite = button.getAttribute('data-is-favorite') === '1';
}
var action = isFavorite ? 'removeFavorite' : 'addFavorite';
// 显示加载状态
var originalText = button.textContent;
var originalTitle = button.title;
button.textContent = '处理中...';
button.disabled = true;
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=' + action + '&feed_id=' + feedId,
credentials: 'include'
})
.then(response => response.json())
.then(data => {
// 恢复按钮状态
button.disabled = false;
if (data.success) {
if (isFavorite) {
// 取消收藏
button.textContent = '收藏';
button.title = '收藏文章';
button.classList.remove('active');
button.setAttribute('data-is-favorite', '0');
showRssMessage(data.message, 'success');
// 如果当前在收藏页面,刷新页面
if (window.location.href.includes('favorites=1')) {
setTimeout(() => {
window.location.reload();
}, 1000);
}
} else {
// 添加收藏
button.textContent = '已藏';
button.title = '已藏,点击取消';
button.classList.add('active');
button.setAttribute('data-is-favorite', '1');
showRssMessage(data.message, 'success');
}
} else {
// 操作失败,恢复原始状态
button.textContent = originalText;
button.title = originalTitle;
showRssMessage(data.message, 'error');
}
})
.catch(error => {
console.error('收藏操作失败:', error);
// 恢复原始状态
button.textContent = originalText;
button.title = originalTitle;
button.disabled = false;
showRssMessage('操作失败: ' + error.message, 'error');
});
}
// 显示统计信息
function showStats() {
showLoading();
// 同时获取缓存统计和RSS定时任务统计
Promise.all([
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=getCacheStats',
credentials: 'include'
}).then(r => r.json()),
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=getRssCronStats',
credentials: 'include'
}).then(r => r.json())
]).then(([cacheData, cronData]) => {
hideLoading();
if (cacheData.success && cronData.success) {
var cacheStats = cacheData.data;
var rssCronStats = cronData.data;
var html = '';
html += '<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 20px;">';
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;">' + (cacheStats.total || 0) + '</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: #28a745;">' + (cacheStats.fresh || 0) + '</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: #dc3545;">' + (cacheStats.stale || 0) + '</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;">活跃RSS源</div>';
html += '<div style="font-size: 24px; font-weight: bold; color: #6f42c1;">' + (cacheStats.url_stats.url_count || 0) + '</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: 14px; font-weight: 500;color:#000;">' + (cacheStats.last_cached ? formatBeijingDateJS(cacheStats.last_cached) : '无记录') + '</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: 14px; font-weight: 500;color:#000;">' + (rssCronStats.last_executed ? formatBeijingDateJS(rssCronStats.last_executed) : '从未执行') + '</div>';
html += '</div>';
html += '</div>';
showModal('缓存统计信息', html);
} else {
var errorMsg = '';
if (!cacheData.success) errorMsg += '缓存统计:' + cacheData.message + ' ';
if (!cronData.success) errorMsg += '定时任务统计:' + cronData.message;
showRssMessage('获取统计信息失败:' + errorMsg, 'error');
}
}).catch(error => {
hideLoading();
showRssMessage('获取失败:' + error.message, 'error');
});
}
// 显示RSS定时任务统计修复版
function showRssCronStats() {
showLoading();
// 同时获取所有需要的数据
Promise.all([
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=getRssCronStats',
credentials: 'include'
}).then(r => r.json()),
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=getRefreshLogs&type=failed&pageSize=5',
credentials: 'include'
}).then(r => r.json()),
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=getRefreshLogs&type=success&pageSize=5',
credentials: 'include'
}).then(r => r.json())
]).then(([statsData, failedData, successData]) => {
hideLoading();
if (!statsData.success) {
showRssMessage('获取统计信息失败:' + statsData.message, 'error');
return;
}
var stats = statsData.data;
var failures = failedData.success ? failedData.data.logs : [];
var successes = successData.success ? successData.data.logs : [];
var html = '';
// 1. 统计概览
html += '<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 20px;">';
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;">' + (stats.total || 0) + '</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: #28a745;">' + (stats.success || 0) + '</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: #dc3545;">' + (stats.failed || 0) + '</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: ' + ((stats.success_rate || 0) > 80 ? '#28a745' : '#ffc107') + ';">' + (stats.success_rate || 0) + '%</div>';
html += '</div>';
html += '</div>';
// 2. 成功率进度条
html += '<div style="margin-bottom: 20px;">';
html += '<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">';
html += '<span style="font-size: 14px;">成功率进度</span>';
html += '<span style="font-size: 14px; font-weight: 500;">' + (stats.success_rate || 0) + '%</span>';
html += '</div>';
html += '<div style="background: #e9ecef; height: 10px; border-radius: 5px; overflow: hidden;">';
html += '<div style="background: ' + ((stats.success_rate || 0) > 80 ? '#28a745' : ((stats.success_rate || 0) > 50 ? '#ffc107' : '#dc3545')) + '; height: 100%; width: ' + (stats.success_rate || 0) + '%;"></div>';
html += '</div>';
html += '</div>';
// 3. 最后执行时间
if (stats.last_executed) {
html += '<div style="margin-bottom: 20px;">';
html += '<div style="font-size: 14px; color: #6c757d; margin-bottom: 5px;">最后执行时间</div>';
html += '<div style="font-size: 16px; font-weight: 500; color: #2c3e50;">' + formatBeijingDateJS(stats.last_executed) + '</div>';
html += '</div>';
}
// 4. 最近失败的网址
if (failures.length > 0) {
html += '<div style="margin-bottom: 25px;">';
html += '<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">';
html += '<h4 style="margin: 0; color: #2c3e50;">最近失败的网址</h4>';
html += '<span style="font-size: 12px; color: #dc3545;">最近24小时内共 ' + failures.length + ' 个</span>';
html += '</div>';
html += '<div style="background: #fff5f5; border: 1px solid #f5c6cb; border-radius: 8px; overflow: hidden;">';
html += '<div style="max-height: 200px; overflow-y: auto;">';
html += '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
html += '<thead>';
html += '<tr style="background: #f8d7da; color: #721c24; border-bottom: 1px solid #f5c6cb;">';
html += '<th style="padding: 10px; text-align: left; font-weight: 500;width:100px;">网站名称</th>';
html += '<th style="padding: 10px; text-align: left; font-weight: 500;">错误信息</th>';
// html += '<th style="padding: 10px; text-align: left; font-weight: 500; width: 100px;">操作</th>';
html += '</tr>';
html += '</thead>';
html += '<tbody>';
failures.forEach(function(failure, index) {
var errorMsg = failure.last_error || '未知错误';
if (errorMsg.length > 80) {
errorMsg = errorMsg.substring(0, 80) + '...';
}
html += '<tr style="border-bottom: 1px solid #f8d7da;' + (index % 2 === 0 ? ' background: #fff;' : ' background: #f8f9fa;') + '">';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="font-weight: 500; color: #2c3e50; margin-bottom: 4px;">' + escapeHtml(failure.site_name || failure.url || '未命名') + '</div>';
if (failure.category_name) {
html += '<span style="background: #e9f7fe; color: #467b96; padding: 2px 6px; border-radius: 3px; font-size: 11px; display: inline-block;">' + escapeHtml(failure.category_name) + '</span>';
}
html += '</td>';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="color: #dc3545; font-size: 11px;" title="' + escapeHtml(failure.last_error || '') + '">' + escapeHtml(errorMsg) + '</div>';
html += '</td>';
//html += '<td style="padding: 10px; vertical-align: top;">';
//html += '<button type="button" class="action-btn" onclick="retryFailedUrl(\'' + failure.id + '\')" style="padding: 4px 8px; font-size: 11px;">重试</button>';
// html += '</td>';
html += '</tr>';
});
html += '</tbody>';
html += '</table>';
html += '</div>';
html += '</div>';
html += '<div style="display: flex; gap: 10px; margin-top: 10px; justify-content: flex-end;">';
html += '<button type="button" class="action-btn" onclick="showAllFailedUrls()" style="padding: 5px 12px; font-size: 12px;">查看全部失败网址</button>';
// html += '<button type="button" class="action-btn" onclick="retryAllFailedUrls()" style="padding: 5px 12px; font-size: 12px; background: #28a745; color: white;">重试全部</button>';
html += '</div>';
html += '</div>';
}
// 5. 最近成功的网址
if (successes.length > 0) {
html += '<div style="margin-bottom: 25px;">';
html += '<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">';
html += '<h4 style="margin: 0; color: #2c3e50;">最近成功的网址</h4>';
html += '<span style="font-size: 12px; color: #28a745;">最近24小时内共 ' + successes.length + ' 个</span>';
html += '</div>';
html += '<div style="background: #d4edda; border: 1px solid #c3e6cb; border-radius: 8px; overflow: hidden;">';
html += '<div style="max-height: 200px; overflow-y: auto;">';
html += '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
html += '<thead>';
html += '<tr style="background: #c3e6cb; color: #155724; border-bottom: 1px solid #b1dfbb;">';
html += '<th style="padding: 10px; text-align: left; font-weight: 500;">网站名称</th>';
html += '<th style="padding: 10px; text-align: left; font-weight: 500;">RSS地址</th>';
html += '<th style="padding: 10px; text-align: left; font-weight: 500; width: 120px;">最后刷新</th>';
html += '</tr>';
html += '</thead>';
html += '<tbody>';
successes.forEach(function(success, index) {
var rssUrl = success.rss_url || '';
if (rssUrl.length > 60) {
rssUrl = rssUrl.substring(0, 60) + '...';
}
html += '<tr style="border-bottom: 1px solid #c3e6cb;' + (index % 2 === 0 ? ' background: #fff;' : ' background: #f8f9fa;') + '">';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="font-weight: 500; color: #155724; margin-bottom: 4px;">' + escapeHtml(success.site_name || success.url || '未命名') + '</div>';
if (success.category_name) {
html += '<span style="background: #d1ecf1; color: #0c5460; padding: 2px 6px; border-radius: 3px; font-size: 11px; display: inline-block;">' + escapeHtml(success.category_name) + '</span>';
}
html += '</td>';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="color: #155724; font-family: monospace; font-size: 11px;" title="' + escapeHtml(success.rss_url) + '">' + escapeHtml(rssUrl) + '</div>';
html += '</td>';
html += '<td style="padding: 10px; vertical-align: top; color: #6c757d; font-size: 11px;">';
html += success.last_refresh_beijing || formatBeijingDateJS(success.last_refresh);
html += '</td>';
html += '</tr>';
});
html += '</tbody>';
html += '</table>';
html += '</div>';
html += '</div>';
html += '<div style="display: flex; gap: 10px; margin-top: 10px; justify-content: flex-end;">';
html += '<button type="button" class="action-btn" onclick="showAllSuccessUrls()" style="padding: 5px 12px; font-size: 12px; background: #28a745; color: white;">查看全部成功网址</button>';
html += '</div>';
html += '</div>';
}
// 6. 如果没有数据
if (failures.length === 0 && successes.length === 0) {
html += '<div style="text-align: center; padding: 40px; color: #6c757d; background: #f8f9fa; border-radius: 8px; margin-bottom: 20px;">';
html += '<div style="font-size: 48px; margin-bottom: 15px;">📊</div>';
html += '<div style="font-weight: 500; margin-bottom: 10px;">暂无最近刷新记录</div>';
html += '<div style="font-size: 13px;">最近24小时内没有成功或失败的刷新记录</div>';
html += '</div>';
}
// 7. 底部操作按钮
html += '<div style="display: flex; gap: 10px; margin-top: 20px; border-top: 1px solid #e8ebf0; padding-top: 20px;">';
html += '<button type="button" class="action-btn" onclick="showRssCronLogs()">查看详细日志</button>';
//html += '<button type="button" class="action-btn" onclick="loadRssCronStats()" style="background: #467b96; color: white;">刷新统计</button>';
html += '</div>';
showModal('RSS定时任务统计', html);
}).catch(error => {
hideLoading();
showRssMessage('获取统计信息失败:' + error.message, 'error');
});
}
// 查看网址详情
function viewUrlDetails(urlId) {
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=getUrlDetails&url_id=' + urlId,
credentials: 'include'
}).then(response => response.json()).then(data => {
hideLoading();
if (data.success) {
var url = data.data;
var html = '';
html += '<div style="max-width: 500px;">';
html += '<h4 style="margin-top: 0; color: #2c3e50;">网址详情</h4>';
html += '<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 15px;">';
html += '<div style="margin-bottom: 10px;">';
html += '<div style="font-size: 12px; color: #6c757d;">网站名称</div>';
html += '<div style="font-weight: 500; color: #2c3e50;">' + escapeHtml(url.title || url.url || '未命名') + '</div>';
html += '</div>';
html += '<div style="margin-bottom: 10px;">';
html += '<div style="font-size: 12px; color: #6c757d;">网站地址</div>';
html += '<div><a href="' + escapeHtml(url.url || '#') + '" target="_blank" style="color: #467b96; text-decoration: none;">' + escapeHtml(url.url || '') + '</a></div>';
html += '</div>';
html += '<div style="margin-bottom: 10px;">';
html += '<div style="font-size: 12px; color: #6c757d;">RSS地址</div>';
html += '<div><a href="' + escapeHtml(url.rss_url || '#') + '" target="_blank" style="color: #467b96; text-decoration: none;">' + escapeHtml(url.rss_url || '') + '</a></div>';
html += '</div>';
if (url.category_name) {
html += '<div style="margin-bottom: 10px;">';
html += '<div style="font-size: 12px; color: #6c757d;">分类</div>';
html += '<div><span style="background: #e9f7fe; color: #467b96; padding: 4px 8px; border-radius: 4px; font-size: 12px;">' + escapeHtml(url.category_name) + '</span></div>';
html += '</div>';
}
if (url.star_rating) {
html += '<div style="margin-bottom: 10px;">';
html += '<div style="font-size: 12px; color: #6c757d;">星级</div>';
html += '<div style="color: #ffc107;">';
for (var i = 1; i <= 3; i++) {
html += (i <= url.star_rating) ? '★' : '☆';
}
html += '</div>';
html += '</div>';
}
if (url.last_refresh) {
html += '<div style="margin-bottom: 10px;">';
html += '<div style="font-size: 12px; color: #6c757d;">最后刷新时间</div>';
html += '<div>' + formatBeijingDateJS(url.last_refresh) + '</div>';
html += '</div>';
}
if (url.last_error) {
html += '<div style="margin-bottom: 10px;">';
html += '<div style="font-size: 12px; color: #6c757d;">最后错误</div>';
html += '<div style="color: #dc3545; font-size: 12px;">' + escapeHtml(url.last_error) + '</div>';
html += '</div>';
}
html += '</div>';
html += '<div style="display: flex; gap: 10px; justify-content: flex-end;">';
html += '<button type="button" class="action-btn" onclick="showRssCronStats()">返回统计</button>';
html += '<button type="button" class="action-btn" onclick="testRssUrl(\'' + url.id + '\')" style="background: #28a745; color: white;">测试RSS</button>';
html += '</div>';
html += '</div>';
showModal('网址详情', html);
} else {
showRssMessage('获取网址详情失败: ' + data.message, 'error');
}
}).catch(error => {
hideLoading();
showRssMessage('获取失败: ' + error.message, 'error');
});
}
// 测试RSS网址
function testRssUrl(urlId) {
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=testRssUrl&url_id=' + urlId,
credentials: 'include'
}).then(response => response.json()).then(data => {
hideLoading();
if (data.success) {
var result = data.data;
var html = '';
html += '<div style="max-width: 500px;">';
html += '<h4 style="margin-top: 0; color: #2c3e50;">RSS测试结果</h4>';
if (result.success) {
html += '<div style="background: #d4edda; padding: 15px; border-radius: 8px; margin-bottom: 15px;">';
html += '<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;">';
html += '<div style="font-size: 24px; color: #28a745;">✓</div>';
html += '<div style="font-weight: 500; color: #155724;">测试成功</div>';
html += '</div>';
if (result.feed_title) {
html += '<div style="margin-bottom: 8px;">';
html += '<div style="font-size: 12px; color: #6c757d;">RSS标题</div>';
html += '<div style="font-weight: 500;">' + escapeHtml(result.feed_title) + '</div>';
html += '</div>';
}
if (result.feed_description) {
html += '<div style="margin-bottom: 8px;">';
html += '<div style="font-size: 12px; color: #6c757d;">RSS描述</div>';
html += '<div style="font-size: 13px;">' + escapeHtml(result.feed_description.substring(0, 100)) + (result.feed_description.length > 100 ? '...' : '') + '</div>';
html += '</div>';
}
if (result.item_count > 0) {
html += '<div style="margin-bottom: 8px;">';
html += '<div style="font-size: 12px; color: #6c757d;">文章数量</div>';
html += '<div>' + result.item_count + ' 篇文章</div>';
html += '</div>';
}
if (result.latest_article) {
html += '<div>';
html += '<div style="font-size: 12px; color: #6c757d;">最新文章</div>';
html += '<div style="font-size: 13px;">' + escapeHtml(result.latest_article.substring(0, 80)) + (result.latest_article.length > 80 ? '...' : '') + '</div>';
html += '</div>';
}
html += '</div>';
} else {
html += '<div style="background: #f8d7da; padding: 15px; border-radius: 8px; margin-bottom: 15px;">';
html += '<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;">';
html += '<div style="font-size: 24px; color: #dc3545;">✗</div>';
html += '<div style="font-weight: 500; color: #721c24;">测试失败</div>';
html += '</div>';
if (result.error) {
html += '<div>';
html += '<div style="font-size: 12px; color: #721c24;">错误信息</div>';
html += '<div style="font-size: 13px; color: #dc3545;">' + escapeHtml(result.error) + '</div>';
html += '</div>';
}
html += '</div>';
}
html += '<div style="display: flex; gap: 10px; justify-content: flex-end;">';
html += '<button type="button" class="action-btn" onclick="showRssCronStats()">返回统计</button>';
html += '</div>';
html += '</div>';
showModal('RSS测试结果', html);
} else {
showRssMessage('测试失败: ' + data.message, 'error');
}
}).catch(error => {
hideLoading();
showRssMessage('测试失败: ' + error.message, 'error');
});
}
// 查看所有成功网址
function showAllSuccessUrls() {
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=getRefreshLogs&type=success&pageSize=50',
credentials: 'include'
}).then(response => response.json()).then(data => {
hideLoading();
if (data.success) {
var successes = data.data.logs;
var html = '';
if (successes.length === 0) {
html += '<div style="text-align: center; padding: 30px; color: #6c757d;">暂无成功网址记录</div>';
} else {
html += '<div style="margin-bottom: 15px; color: #6c757d; font-size: 13px;">';
html += '共找到 ' + successes.length + ' 个成功网址';
html += '</div>';
html += '<div style="max-height: 400px; overflow-y: auto;">';
html += '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
html += '<thead>';
html += '<tr style="background: #d4edda; border-bottom: 2px solid #c3e6cb;">';
html += '<th style="padding: 10px; text-align: left;">网站名称</th>';
html += '<th style="padding: 10px; text-align: left;">RSS地址</th>';
html += '<th style="padding: 10px; text-align: left;">最后刷新</th>';
html += '<th style="padding: 10px; text-align: left;">状态</th>';
html += '</tr>';
html += '</thead>';
html += '<tbody>';
successes.forEach(function(success, index) {
var lastRefresh = success.last_refresh_beijing || formatBeijingDateJS(success.last_refresh);
html += '<tr style="border-bottom: 1px solid #c3e6cb;' + (index % 2 === 0 ? ' background: #fff;' : ' background: #f8f9fa;') + '">';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="font-weight: 500; color: #155724; margin-bottom: 4px;">' + escapeHtml(success.site_name || success.url || '未命名') + '</div>';
if (success.category_name) {
html += '<span style="background: #d1ecf1; color: #0c5460; padding: 2px 6px; border-radius: 3px; font-size: 11px; display: inline-block;">' + escapeHtml(success.category_name) + '</span>';
}
html += '</td>';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="color: #155724; font-family: monospace; font-size: 11px; word-break: break-all;" title="' + escapeHtml(success.rss_url) + '">' + escapeHtml(success.rss_url) + '</div>';
html += '</td>';
html += '<td style="padding: 10px; vertical-align: top; color: #6c757d; font-size: 11px;">';
html += lastRefresh;
html += '</td>';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<span style="color: #28a745; font-size: 11px; font-weight: 500;">成功</span>';
html += '</td>';
html += '</tr>';
});
html += '</tbody>';
html += '</table>';
html += '</div>';
}
html += '<div style="display: flex; justify-content: space-between; margin-top: 20px;">';
//html += '<button type="button" class="action-btn" onclick="exportSuccessUrls()" style="padding: 8px 16px; background: #28a745; color: white;">导出列表</button>';
html += '<button type="button" class="action-btn" onclick="showRssCronStats()" style="padding: 8px 16px;">返回统计</button>';
html += '</div>';
showModal('所有成功网址', html);
} else {
showRssMessage('获取成功网址列表失败:' + data.message, 'error');
}
}).catch(error => {
hideLoading();
showRssMessage('获取失败:' + error.message, 'error');
});
}
// 导出成功网址
function exportSuccessUrls() {
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=exportSuccessUrls',
credentials: 'include'
}).then(response => response.json()).then(data => {
hideLoading();
if (data.success && data.csv_data) {
// 创建并下载CSV文件
const blob = new Blob([data.csv_data], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `success-urls-${new Date().toISOString().slice(0,10)}.csv`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showRssMessage('成功网址列表已导出', 'success');
} else {
showRssMessage('导出失败:' + data.message, 'error');
}
}).catch(error => {
hideLoading();
showRssMessage('导出失败:' + error.message, 'error');
});
}
// 重试单个失败网址
function retryFailedUrl(urlId) {
if (!confirm('确定要重试这个网址的RSS刷新吗')) {
return;
}
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=retryRssUrl&url_id=' + urlId,
credentials: 'include'
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
showRssMessage('重试成功:' + data.message, 'success');
// 刷新统计信息
setTimeout(() => {
loadRssCronStats();
if (typeof showRssCronStats === 'function') {
showRssCronStats();
}
}, 1000);
} else {
showRssMessage('重试失败:' + data.message, 'error');
}
})
.catch(error => {
hideLoading();
showRssMessage('重试失败:' + error.message, 'error');
});
}
// 忽略失败网址(标记为不活跃)
function ignoreFailedUrl(urlId) {
if (!confirm('确定要忽略这个网址吗?\n\n忽略后该网址将不再参与自动刷新。您可以在网址管理中重新启用它。')) {
return;
}
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=ignoreFailedUrl&url_id=' + urlId,
credentials: 'include'
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
showRssMessage('已忽略该网址', 'success');
// 刷新统计信息
setTimeout(() => {
loadRssCronStats();
if (typeof showRssCronStats === 'function') {
showRssCronStats();
}
}, 1000);
} else {
showRssMessage('操作失败:' + data.message, 'error');
}
})
.catch(error => {
hideLoading();
showRssMessage('操作失败:' + error.message, 'error');
});
}
// 重试所有失败网址
function retryAllFailedUrls() {
if (!confirm('确定要重试所有失败的RSS网址吗\n\n这可能需要一些时间。')) {
return;
}
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=retryAllFailedUrls',
credentials: 'include'
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
showRssMessage('批量重试完成:' + data.message, 'success');
// 刷新统计信息
setTimeout(() => {
loadRssCronStats();
if (typeof showRssCronStats === 'function') {
showRssCronStats();
}
}, 2000);
} else {
showRssMessage('批量重试失败:' + data.message, 'error');
}
})
.catch(error => {
hideLoading();
showRssMessage('批量重试失败:' + error.message, 'error');
});
}
// 查看所有失败网址
function showAllFailedUrls() {
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=getRefreshLogs&type=failed&pageSize=50',
credentials: 'include'
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
var failures = data.data.logs;
var html = '';
if (failures.length === 0) {
html += '<div style="text-align: center; padding: 30px; color: #6c757d;">暂无失败网址记录</div>';
} else {
html += '<div style="margin-bottom: 15px; color: #6c757d; font-size: 13px;">';
html += '共找到 ' + failures.length + ' 个失败网址';
html += '</div>';
html += '<div style="max-height: 400px; 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:#000;">网站名称</th>';
html += '<th style="padding: 10px; text-align: left;color:#000;width:100px;">RSS地址</th>';
html += '<th style="padding: 10px; text-align: left;color:#000;width:80px;">最后刷新</th>';
html += '<th style="padding: 10px; text-align: left;color:#000;">错误原因</th>';
//html += '<th style="padding: 10px; text-align: left;color:#000;">操作</th>';
html += '</tr>';
html += '</thead>';
html += '<tbody>';
failures.forEach(function(failure, index) {
var lastRefresh = failure.last_refresh_beijing || formatBeijingDateJS(failure.last_refresh);
var errorMsg = failure.last_error || '未知错误';
var statusClass = failure.is_active ? 'status-active' : 'status-inactive';
var statusText = failure.is_active ? '活跃' : '已停用';
html += '<tr style="border-bottom: 1px solid #f0f2f5;' + (!failure.is_active ? ' background: #f8f9fa; opacity: 0.7;' : '') + '">';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="font-weight: 500; margin-bottom: 4px;color:#000;">' + escapeHtml(failure.site_name || failure.url || '未命名') + '</div>';
if (failure.category_name) {
html += '<span style="background: #e9f7fe; color: #467b96; padding: 2px 6px; border-radius: 3px; font-size: 11px; display: inline-block;">' + escapeHtml(failure.category_name) + '</span>';
}
html += '</td>';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="color: #467b96; font-family: monospace; font-size: 11px; word-break: break-all;" title="' + escapeHtml(failure.rss_url) + '">' + escapeHtml(failure.rss_url) + '</div>';
html += '</td>';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="color: #6c757d; font-size: 11px;">' + lastRefresh + '</div>';
if (!failure.is_active) {
html += '<span style="background: #6c757d; color: white; padding: 2px 6px; border-radius: 3px; font-size: 10px; display: inline-block; margin-top: 3px;">已停用</span>';
}
html += '</td>';
html += '<td style="padding: 10px; vertical-align: top;">';
html += '<div style="color: #dc3545; font-size: 11px;" title="' + escapeHtml(errorMsg) + '">' + escapeHtml(errorMsg.length > 100 ? errorMsg.substring(0, 100) + '...' : errorMsg) + '</div>';
html += '</td>';
// html += '<td style="padding: 10px; vertical-align: top;">';
// html += '<div style="display: flex; flex-direction: column; gap: 5px;">';
// if (failure.is_active) {
// html += '<button type="button" class="action-btn" onclick="retryFailedUrl(\'' + failure.id + '\')" style="padding: 4px 8px; font-size: 11px;">重试</button>';
//html += '<button type="button" class="action-btn" onclick="ignoreFailedUrl(\'' + failure.id + '\')" style="padding: 4px 8px; font-size: 11px; background: #6c757d; color: white;">停用</button>';
// } else {
// html += '<button type="button" class="action-btn" onclick="enableFailedUrl(\'' + failure.id + '\')" style="padding: 4px 8px; font-size: 11px; background: #28a745; color: white;">启用</button>';
// }
// html += '</div>';
//html += '</td>';
html += '</tr>';
});
html += '</tbody>';
html += '</table>';
html += '</div>';
}
html += '<div style="display: flex; justify-content: space-between; margin-top: 20px;">';
//html += '<button type="button" class="action-btn" onclick="exportFailedUrls()" style="padding: 8px 16px;">导出列表</button>';
html += '<button type="button" class="action-btn" onclick="showRssCronStats()" style="padding: 8px 16px;">返回统计</button>';
html += '</div>';
showModal('所有失败网址', html);
} else {
showRssMessage('获取失败网址列表失败:' + data.message, 'error');
}
})
.catch(error => {
hideLoading();
showRssMessage('获取失败:' + error.message, 'error');
});
}
// 启用已停用的失败网址
function enableFailedUrl(urlId) {
if (!confirm('确定要重新启用这个网址吗?')) {
return;
}
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=enableFailedUrl&url_id=' + urlId,
credentials: 'include'
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
showRssMessage('网址已重新启用', 'success');
// 刷新列表
setTimeout(() => {
showAllFailedUrls();
}, 1000);
} else {
showRssMessage('启用失败:' + data.message, 'error');
}
})
.catch(error => {
hideLoading();
showRssMessage('启用失败:' + error.message, 'error');
});
}
// 导出失败网址列表
function exportFailedUrls() {
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'do=getAllFailedUrls',
credentials: 'include'
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success && data.csv_data) {
// 创建并下载CSV文件
const blob = new Blob([data.csv_data], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `failed-urls-${new Date().toISOString().slice(0,10)}.csv`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showRssMessage('失败网址列表已导出', 'success');
} else {
showRssMessage('导出失败:' + data.message, 'error');
}
})
.catch(error => {
hideLoading();
showRssMessage('导出失败:' + error.message, 'error');
});
}
// 显示RSS定时任务日志两行显示版
function showRssCronLogs() {
showLoading();
fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=getCronLogs&type=rss&limit=50',
credentials: 'include'
})
.then(res => res.json())
.then(data => {
hideLoading();
if (!data.success) {
showRssMessage('获取日志失败:' + data.message, 'error');
return;
}
var logs = data.data || [];
var html = '';
if (logs.length === 0) {
html += '<div style="text-align: center; padding: 30px; color: #6c757d;">暂无RSS定时任务日志</div>';
} else {
html += '<div style="background: #f8f9fa;padding: 15px;border-radius: 8px;">';
html += '<table style="width: 100%; border-collapse: collapse; font-size: 13px;">';
html += '<thead>';
html += '<tr style="background: #f8f9fa; border-bottom: 2px solid #dee2e6;">';
html += '<th style="padding: 12px; text-align: left; color: #6c757d; width: 150px;">时间</th>';
html += '<th style="padding: 12px; text-align: left; color: #6c757d; width: 100px;">类型</th>';
html += '<th style="padding: 12px; text-align: left; color: #6c757d;width:200px;">结果详情</th>';
html += '<th style="padding: 12px; text-align: left; color: #6c757d; width: 80px;">状态</th>';
html += '</tr>';
html += '</thead>';
html += '<tbody>';
logs.forEach(function(log, index) {
var result = {};
try {
if (log.result) {
result = JSON.parse(log.result);
}
} catch (e) {
console.error('解析result失败:', e);
}
var hasError = log.error_message && log.error_message.trim() !== '';
// 第一行:基本信息
html += '<tr style="border-bottom: 1px solid #f0f2f5;' + (hasError ? ' background: #fff3cd;' : '') + '">';
html += '<td style="padding: 12px;color:#000; vertical-align: center;;">' + formatBeijingDateJS(log.executed_time) + '</td>';
html += '<td style="padding: 12px; vertical-align: center;">';
html += '<span style="background: #e9f7fe; color: #467b96; padding: 4px 10px; border-radius: 4px; display: inline-block; font-size: 12px;">' + log.cron_type + '</span>';
html += '</td>';
// 第一行:结果文本
var resultText = '';
if (result.message) {
// 移除 "刷新完成:" 前缀
resultText = result.message.replace('刷新完成:', '');
} else {
var successCount = result.successCount || result.successcount || 0;
var failureCount = result.failureCount || result.failurecount || 0;
resultText = '成功 ' + successCount + ' 个,失败 ' + failureCount + ' 个';
}
html += '<td style="padding: 12px;color:#000; vertical-align: center;">';
html += '<div style="font-weight: 500; color: #343a40;">' + resultText + '</div>';
html += '</td>';
// 状态列
if (hasError) {
html += '<td style="padding: 12px; vertical-align: center;">';
html += '<span style="color: #dc3545; font-weight: 500; background: #f8d7da; padding: 4px 10px; border-radius: 4px; display: inline-block; font-size: 12px;">失败</span>';
html += '</td>';
} else {
html += '<td style="padding: 12px; vertical-align: center;">';
html += '<span style="color: #28a745; font-weight: 500; background: #d4edda; padding: 4px 10px; border-radius: 4px; display: inline-block; font-size: 12px;">成功</span>';
html += '</td>';
}
html += '</tr>';
// 第二行RSS地址详情跨列显示
var successRssUrls = result.successRssUrls || [];
var failedRssUrls = result.failedRssUrls || [];
if (successRssUrls.length > 0 || failedRssUrls.length > 0) {
html += '<tr style="border-bottom: 1px solid #f0f2f5;">';
html += '<td colspan="4" style="padding: 10px;">';
// 成功地址
if (successRssUrls.length > 0) {
html += '<div style="margin-bottom: 12px;">';
html += '<div style="color: #28a745; font-weight: 500; margin-bottom: 6px; font-size: 12px; display: flex; align-items: center;">';
html += '<span style="background: #28a745; color: white; width: 20px; height: 20px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; margin-right: 8px; font-size: 10px;">✓</span>';
html += '成功地址 (' + successRssUrls.length + '个)';
html += '</div>';
html += '<div style="display: flex; flex-wrap: wrap; gap: 6px;">';
successRssUrls.forEach(function(url, idx) {
var displayUrl = url;
if (displayUrl.length > 80) {
displayUrl = displayUrl.substring(0, 80) + '...';
}
html += '<span style="background: #f8f9fa; color: #155724; padding: 4px 10px; border-radius: 4px; font-size: 11px; border: 1px solid #d4edda;" title="' + escapeHtml(url) + '">';
html += escapeHtml(displayUrl);
html += '</span>';
});
html += '</div>';
html += '</div>';
}
// 失败地址
if (failedRssUrls.length > 0) {
html += '<div>';
html += '<div style="color: #dc3545; font-weight: 500; margin-bottom: 6px; font-size: 12px; display: flex; align-items: center;">';
html += '<span style="background: #dc3545; color: white; width: 20px; height: 20px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; margin-right: 8px; font-size: 10px;">✗</span>';
html += '失败地址 (' + failedRssUrls.length + '个)';
html += '</div>';
html += '<div style="display: flex; flex-wrap: wrap; gap: 6px;">';
failedRssUrls.forEach(function(url, idx) {
var displayUrl = url;
// 处理异常标记
if (url.includes('[异常]')) {
displayUrl = url.replace(' [异常]', '');
}
if (displayUrl.length > 80) {
displayUrl = displayUrl.substring(0, 80) + '...';
}
if (url.includes('[异常]')) {
displayUrl += ' [异常]';
}
html += '<span style="background: #f8f9fa; color: #721c24; padding: 4px 10px; border-radius: 4px; font-size: 11px; border: 1px solid #f8d7da; " title="' + escapeHtml(url) + '">';
html += escapeHtml(displayUrl);
html += '</span>';
});
html += '</div>';
html += '</div>';
}
html += '</td>';
html += '</tr>';
}
});
html += '</tbody>';
html += '</table>';
html += '</div>';
}
html += '<div style="display: flex; gap: 10px; margin-top: 20px;">';
html += '<button type="button" class="action-btn" onclick="showRssCronStats()">查看统计</button>';
html += '<span style="color: #6c757d; font-size: 12px; margin-left: auto; padding: 5px 10px; background: #f8f9fa; border-radius: 3px;">';
html += '📋 网址列表独立显示鼠标悬停查看完整URL';
html += '</span>';
html += '</div>';
showModal('RSS定时任务日志', html);
})
.catch(error => {
hideLoading();
showRssMessage('获取失败:' + error.message, 'error');
});
}
// JavaScript中格式化北京时间
function formatBeijingDateJS(datetime) {
if (!datetime) return '';
var date = new Date(datetime);
// 转换为北京时间(+8小时
var beijingTime = new Date(date.getTime() + (8 * 60 * 60 * 1000));
return beijingTime.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).replace(/\//g, '-');
}
// 显示模态框
function showModal(title, content) {
var modal = document.getElementById('stats-modal');
var modalTitle = document.getElementById('modal-title');
var modalContent = document.getElementById('modal-content');
modalTitle.textContent = title;
modalContent.innerHTML = content;
modal.style.display = 'flex';
}
// 关闭模态框
function closeModal() {
var modal = document.getElementById('stats-modal');
modal.style.display = 'none';
}
// 绑定刷新按钮事件
function bindRefreshButton(buttonId) {
var btn = document.getElementById(buttonId);
if (btn) {
btn.addEventListener('click', function(e) {
e.preventDefault();
if (confirm('确定要手动刷新RSS信息吗\n\n这将获取所有RSS源的最新文章可能需要一些时间。')) {
refreshRss();
}
});
}
}
// ==================== 新增:阅读全文功能相关函数 ====================
// 显示阅读全文弹窗
function showFullContent(articleData) {
var modal = document.getElementById('full-content-modal');
var titleElement = document.getElementById('full-content-title');
var siteTitleElement = document.getElementById('full-content-site-title');
var originalTitleElement = document.getElementById('full-content-original-title');
var metaElement = document.getElementById('full-content-meta');
var bodyElement = document.getElementById('full-content-body');
var statusElement = document.getElementById('full-content-status');
// 设置标题
var articleTitle = articleData.title || '无标题';
var titleText = (articleTitle.length > 50 ? articleTitle.substring(0, 50) + '...' : articleTitle);
titleElement.textContent = titleText;
siteTitleElement.textContent = '来源:' + (articleData.site_title || '未知网站');
originalTitleElement.textContent = articleTitle;
// 设置元信息
var metaHtml = '';
if (articleData.category) {
metaHtml += '<div class="meta-item"><span>分类:</span><i>' + escapeHtml(articleData.category) + '</i></div>';
}
if (articleData.pub_date) {
metaHtml += '<div class="meta-item"><span>发布时间:</span><i>' + escapeHtml(articleData.pub_date) + '</i></div>';
}
if (articleData.site_url) {
var siteUrl = articleData.site_url || '#';
var siteTitle = articleData.site_title || articleData.site_url || '访问网站';
metaHtml += '<div class="meta-item"><span>网站:</span><i><a href="' + escapeHtml(siteUrl) + '" target="_blank" style="color: #467b96; text-decoration: none;">' + escapeHtml(siteTitle) + '</a></i></div>';
}
metaElement.innerHTML = metaHtml;
// 处理文章内容
var content = articleData.full_content || articleData.description || '暂无内容';
// 清理和格式化内容
var formattedContent = formatArticleContent(content);
bodyElement.innerHTML = formattedContent;
// 更新状态
var contentLength = content.length;
var wordCount = contentLength > 0 ? Math.ceil(contentLength / 3) : 0;
statusElement.innerHTML = '字数:' + wordCount;
// 存储当前文章数据供其他函数使用
window.currentArticleData = articleData;
// 显示弹窗
modal.style.display = 'flex';
// 滚动到顶部
var contentArea = modal.querySelector('.full-content-body');
if (contentArea) {
contentArea.scrollTop = 0;
}
// 添加键盘快捷键
document.addEventListener('keydown', handleFullContentKeyboard);
}
// 关闭阅读全文弹窗
function closeFullContentModal() {
var modal = document.getElementById('full-content-modal');
modal.style.display = 'none';
window.currentArticleData = null;
// 移除键盘快捷键
document.removeEventListener('keydown', handleFullContentKeyboard);
}
// 处理阅读全文弹窗的键盘快捷键
function handleFullContentKeyboard(e) {
if (e.key === 'Escape') {
closeFullContentModal();
} else if (e.key === 'c' && (e.ctrlKey || e.metaKey)) {
copyFullContentLink();
e.preventDefault();
} else if (e.key === 'o' && (e.ctrlKey || e.metaKey)) {
openOriginalLink();
e.preventDefault();
}
}
// 复制文章链接
function copyFullContentLink() {
if (!window.currentArticleData || !window.currentArticleData.link) {
showRssMessage('无法获取文章链接', 'warning');
return;
}
copyToClipboard(window.currentArticleData.link);
}
// 打开原文链接
function openOriginalLink() {
if (!window.currentArticleData || !window.currentArticleData.link) {
showRssMessage('无法获取文章链接', 'warning');
return;
}
window.open(window.currentArticleData.link, '_blank');
}
// 格式化文章内容 - 安全渲染HTML版本
function formatArticleContent(content) {
if (!content || content.trim() === '') {
return '<div style="text-align: center; padding: 40px; color: #6c757d;"><p>暂无内容</p></div>';
}
try {
// 先清理内容但保留安全的HTML标签
content = content.trim();
// 移除可能有害的标签和属性
content = sanitizeHtml(content);
// 处理段落 - 确保<p>标签正确闭合
content = content.replace(/<p>/g, '<p style="margin-bottom: 15px; line-height: 1.6;">');
// 处理图片 - 添加懒加载和响应式
content = content.replace(/<img([^>]+)>/gi, function(match, attrs) {
// 提取src
var srcMatch = attrs.match(/src=["']([^"']+)["']/);
var altMatch = attrs.match(/alt=["']([^"']+)["']/);
var src = srcMatch ? srcMatch[1] : '';
var alt = altMatch ? altMatch[1] : '图片';
if (src) {
return '<img src="' + src + '" alt="' + alt + '" ' +
'style="max-width: 100%; height: auto; border-radius: 6px; margin: 10px 0; display: block;" ' +
'loading="lazy" onerror="this.style.display=\'none\'">';
}
return match;
});
// 处理链接 - 添加安全属性
content = content.replace(/<a([^>]+)>/gi, function(match, attrs) {
var hrefMatch = attrs.match(/href=["']([^"']+)["']/);
if (hrefMatch) {
var href = hrefMatch[1];
// 确保外部链接在新窗口打开
if (href.startsWith('http')) {
return '<a href="' + href + '" target="_blank" rel="noopener noreferrer"' +
' style="color: #467b96; text-decoration: none;">';
}
}
return '<a href="#" style="color: #467b96; text-decoration: none;">';
});
// 处理引用块
content = content.replace(/<blockquote>/g,
'<blockquote style="border-left: 4px solid #467b96; padding-left: 15px; margin: 15px 0; color: #6c757d; font-style: italic; background: #f8f9fa; padding: 12px 15px;">');
// 处理代码
content = content.replace(/<code>/g,
'<code style="background: #f5f7fa; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.9em; color: #467b96;">');
// 处理预格式化文本
content = content.replace(/<pre>/g,
'<pre style="background: #2c3e50; color: #ecf0f1; padding: 15px; border-radius: 6px; overflow-x: auto; margin: 15px 0; font-family: monospace; font-size: 13px; line-height: 1.4;">');
// 添加响应式容器
content = '<div style="max-width: 100%; overflow-wrap: break-word; word-wrap: break-word;">' +
content +
'</div>';
return content;
} catch (error) {
console.error('格式化文章内容时出错:', error);
// 出错时显示原始内容(安全转义后)
return '<div style="background: #f8f9fa; padding: 15px; border-radius: 6px; white-space: pre-wrap; font-family: monospace; font-size: 13px;">' +
escapeHtml(content) +
'</div>';
}
}
// 安全的HTML清理函数
function sanitizeHtml(html) {
// 只允许安全的标签和属性
var allowedTags = {
'p': ['style', 'class'],
'br': [],
'b': [], 'strong': [],
'i': [], 'em': [],
'u': [],
'a': ['href', 'target', 'rel', 'style', 'title'],
'img': ['src', 'alt', 'title', 'style', 'loading'],
'ul': ['style'],
'ol': ['style'],
'li': ['style'],
'blockquote': ['style'],
'code': ['style'],
'pre': ['style'],
'h1': ['style'], 'h2': ['style'], 'h3': ['style'], 'h4': ['style'], 'h5': ['style'], 'h6': ['style'],
'div': ['style'],
'span': ['style']
};
// 简单的标签清理实际项目应该使用更严格的库如DOMPurify
// 这里做一个基本的安全处理
html = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
html = html.replace(/<style\b[^>]*>[\s\ S]*?<\/style>/gi, '');
html = html.replace(/on\w+="[^"]*"/gi, '');
html = html.replace(/on\w+='[^']*'/gi, '');
html = html.replace(/javascript:/gi, '');
html = html.replace(/data:/gi, '');
return html;
}
// 更新escapeHtml函数不再转义所有HTML
function escapeHtml(text) {
if (!text) return '';
// 只转义真正危险的字符保留HTML标签
var map = {
'&': '&amp;',
'<': '&lt;', // 这行会导致HTML标签被转义但我们需要它
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
// 临时解决方案:先不转义<和>让HTML标签正常工作
// return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
// 简单转义但保留HTML标签
text = String(text);
text = text.replace(/&(?!(amp|lt|gt|quot|#039);)/g, '&amp;');
text = text.replace(/"/g, '&quot;');
text = text.replace(/'/g, '&#039;');
return text;
}
// ==================== 新增:文章状态管理功能 ====================
// 初始化状态管理
function initStatusManagement() {
// 从LocalStorage加载所有状态
loadFeedStatuses();
// 绑定状态选择器点击事件
bindStatusSelectors();
// 初始化状态筛选
updateStatusFilter();
// 添加行选择功能
addRowSelection();
}
// 加载文章状态
function loadFeedStatuses() {
const rows = document.querySelectorAll('.rss-feeds-table tbody tr');
rows.forEach(row => {
const feedId = row.getAttribute('data-feed-id');
const status = localStorage.getItem(`feed_status_${feedId}`) || 'unread';
// 更新状态点
const statusDot = row.querySelector('.status-dot');
if (statusDot) {
statusDot.className = `status-dot status-${status}`;
}
// 只更新状态列的样式,不更新整行
// const statusCell = row.querySelector('.feed-status-cell');
//if (statusCell) {
// statusCell.classList.remove('status-unread', 'status-read', 'status-ignore');
// statusCell.classList.add(`status-${status}`);
// }
});
}
// 保存文章状态
function saveFeedStatus(feedId, status) {
localStorage.setItem(`feed_status_${feedId}`, status);
// 更新界面
const row = document.querySelector(`tr[data-feed-id="${feedId}"]`);
if (row) {
const statusDot = row.querySelector('.status-dot');
if (statusDot) {
statusDot.className = `status-dot status-${status}`;
}
// 只更新状态列的样式,不更新整行
//const statusCell = row.querySelector('.feed-status-cell');
//if (statusCell) {
// statusCell.classList.remove('status-unread', 'status-read', 'status-ignore');
// statusCell.classList.add(`status-${status}`);
// }
}
// 关闭状态下拉菜单
const statusSelector = row.querySelector('.status-selector');
if (statusSelector) {
statusSelector.classList.remove('active');
}
// 更新筛选器计数
updateStatusFilter();
}
// 设置文章状态(从下拉菜单调用)
function setFeedStatus(feedId, status) {
saveFeedStatus(feedId, status);
// 显示操作反馈
const statusNames = {
'unread': '未读',
'read': '已读',
'ignore': '无感'
};
showRssMessage(`文章已标记为"${statusNames[status]}"`, 'success');
}
// 切换状态下拉菜单
function toggleStatusDropdown(selector) {
event.stopPropagation();
// 关闭所有其他状态下拉菜单
document.querySelectorAll('.status-selector').forEach(s => {
if (s !== selector) {
s.classList.remove('active');
}
});
// 切换当前状态下拉菜单
selector.classList.toggle('active');
}
// 绑定状态选择器事件
function bindStatusSelectors() {
const selectors = document.querySelectorAll('.status-selector');
selectors.forEach(selector => {
const options = selector.querySelectorAll('.status-option');
options.forEach(option => {
option.addEventListener('click', function(e) {
e.stopPropagation();
const feedId = this.closest('.status-selector').getAttribute('data-feed-id');
const status = this.getAttribute('data-status');
setFeedStatus(feedId, status);
});
});
});
}
// 更新状态筛选器
function updateStatusFilter() {
const rows = document.querySelectorAll('.rss-feeds-table tbody tr');
let allCount = rows.length;
let unreadCount = 0;
let readCount = 0;
let ignoreCount = 0;
rows.forEach(row => {
const feedId = row.getAttribute('data-feed-id');
const status = localStorage.getItem(`feed_status_${feedId}`) || 'unread';
switch(status) {
case 'unread':
unreadCount++;
break;
case 'read':
readCount++;
break;
case 'ignore':
ignoreCount++;
break;
}
});
// 更新筛选器计数
document.getElementById('count-all').textContent = allCount;
document.getElementById('count-unread').textContent = unreadCount;
document.getElementById('count-read').textContent = readCount;
document.getElementById('count-ignore').textContent = ignoreCount;
// 更新状态筛选按钮文本
const statusFilterBtn = document.getElementById('status-filter-btn');
if (statusFilterBtn) {
let statusText = '状态筛选';
const currentFilter = localStorage.getItem('currentStatusFilter') || 'all';
if (currentFilter !== 'all') {
const statusNames = {
'unread': '未读',
'read': '已读',
'ignore': '无感'
};
statusText = `${statusNames[currentFilter]} (${eval(currentFilter + 'Count')})`;
}
statusFilterBtn.innerHTML = statusText;
}
}
// 切换状态筛选下拉菜单
function toggleStatusFilter() {
event.stopPropagation();
const dropdown = document.getElementById('status-filter-dropdown');
dropdown.classList.toggle('active');
// 关闭批量操作下拉菜单
document.getElementById('batch-dropdown').classList.remove('active');
}
// 切换批量操作下拉菜单
function toggleBatchDropdown() {
event.stopPropagation();
const dropdown = document.getElementById('batch-dropdown');
dropdown.classList.toggle('active');
// 关闭状态筛选下拉菜单
document.getElementById('status-filter-dropdown').classList.remove('active');
}
// 按状态筛选
function filterByStatus(status) {
event.stopPropagation();
// 保存当前筛选状态
localStorage.setItem('currentStatusFilter', status);
// 更新按钮文本
const statusFilterBtn = document.getElementById('status-filter-btn');
if (statusFilterBtn) {
if (status === 'all') {
statusFilterBtn.textContent = '状态筛选';
} else {
const statusNames = {
'unread': '未读',
'read': '已读',
'ignore': '无感'
};
const rows = document.querySelectorAll('.rss-feeds-table tbody tr');
let count = 0;
rows.forEach(row => {
const feedId = row.getAttribute('data-feed-id');
const rowStatus = localStorage.getItem(`feed_status_${feedId}`) || 'unread';
if (rowStatus === status) {
count++;
}
});
statusFilterBtn.textContent = `${statusNames[status]} (${count})`;
}
}
// 显示/隐藏行
const rows = document.querySelectorAll('.rss-feeds-table tbody tr');
let visibleCount = 0;
rows.forEach(row => {
const feedId = row.getAttribute('data-feed-id');
const rowStatus = localStorage.getItem(`feed_status_${feedId}`) || 'unread';
if (status === 'all' || rowStatus === status) {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
});
// 显示筛选结果
const statusNames = {
'all': '全部',
'unread': '未读',
'read': '已读',
'ignore': '无感'
};
showRssMessage(`已筛选${statusNames[status]}文章,显示${visibleCount}条`, 'info');
// 关闭下拉菜单
document.getElementById('status-filter-dropdown').classList.remove('active');
}
// 添加行选择功能
function addRowSelection() {
const rows = document.querySelectorAll('.rss-feeds-table tbody tr');
rows.forEach(row => {
// 点击行可以选择(除了状态选择器区域)
row.addEventListener('click', function(e) {
if (e.target.closest('.status-selector') ||
e.target.closest('.status-dropdown') ||
e.target.tagName === 'A' ||
e.target.tagName === 'BUTTON') {
return;
}
this.classList.toggle('selected');
updateBatchActions();
});
// 双击行标记为已读
row.addEventListener('dblclick', function(e) {
if (e.target.closest('.status-selector') ||
e.target.closest('.status-dropdown')) {
return;
}
const feedId = this.getAttribute('data-feed-id');
setFeedStatus(feedId, 'read');
});
});
}
// 全选
function selectAllFeeds() {
const rows = document.querySelectorAll('.rss-feeds-table tbody tr');
const visibleRows = Array.from(rows).filter(row => row.style.display !== 'none');
visibleRows.forEach(row => {
row.classList.add('selected');
});
updateBatchActions();
showRssMessage(`已选择 ${visibleRows.length} 篇文章`, 'info');
// 关闭下拉菜单
document.getElementById('batch-dropdown').classList.remove('active');
}
// 清除选择
function clearSelection() {
const rows = document.querySelectorAll('.rss-feeds-table tbody tr');
rows.forEach(row => {
row.classList.remove('selected');
});
updateBatchActions();
showRssMessage('已清除所有选择', 'info');
// 关闭下拉菜单
document.getElementById('batch-dropdown').classList.remove('active');
}
// 更新批量操作按钮状态
function updateBatchActions() {
const selectedCount = document.querySelectorAll('.rss-feeds-table tbody tr.selected').length;
// 可以在这里更新批量操作按钮的文本或状态
if (selectedCount > 0) {
// 自动显示消息已在 selectAllFeeds 和 clearSelection 中处理
}
}
// 批量标记为已读
function batchMarkAsRead() {
const selectedRows = document.querySelectorAll('.rss-feeds-table tbody tr.selected');
if (selectedRows.length === 0) {
showRssMessage('请先选择文章', 'warning');
return;
}
selectedRows.forEach(row => {
const feedId = row.getAttribute('data-feed-id');
saveFeedStatus(feedId, 'read');
row.classList.remove('selected');
});
showRssMessage(`已将 ${selectedRows.length} 篇文章标记为已读`, 'success');
updateStatusFilter();
// 关闭下拉菜单
document.getElementById('batch-dropdown').classList.remove('active');
}
// 批量标记为未读
function batchMarkAsUnread() {
const selectedRows = document.querySelectorAll('.rss-feeds-table tbody tr.selected');
if (selectedRows.length === 0) {
showRssMessage('请先选择文章', 'warning');
return;
}
selectedRows.forEach(row => {
const feedId = row.getAttribute('data-feed-id');
saveFeedStatus(feedId, 'unread');
row.classList.remove('selected');
});
showRssMessage(`已将 ${selectedRows.length} 篇文章标记为未读`, 'success');
updateStatusFilter();
// 关闭下拉菜单
document.getElementById('batch-dropdown').classList.remove('active');
}
// 批量标记为无感
function batchMarkAsIgnore() {
const selectedRows = document.querySelectorAll('.rss-feeds-table tbody tr.selected');
if (selectedRows.length === 0) {
showRssMessage('请先选择文章', 'warning');
return;
}
if (!confirm(`确定要将 ${selectedRows.length} 篇文章标记为"无感"吗?\n这些文章将被隐藏除非选择"全部"筛选器。`)) {
return;
}
selectedRows.forEach(row => {
const feedId = row.getAttribute('data-feed-id');
saveFeedStatus(feedId, 'ignore');
row.classList.remove('selected');
});
showRssMessage(`已将 ${selectedRows.length} 篇文章标记为无感`, 'success');
updateStatusFilter();
// 关闭下拉菜单
document.getElementById('batch-dropdown').classList.remove('active');
}
// 清除所有状态(重置功能)
function clearAllStatuses() {
if (!confirm('确定要清除所有文章的状态标记吗?\n这将重置所有文章的未读/已读/无感状态。')) {
return;
}
// 找到所有有状态存储的文章ID
const rows = document.querySelectorAll('.rss-feeds-table tbody tr');
rows.forEach(row => {
const feedId = row.getAttribute('data-feed-id');
localStorage.removeItem(`feed_status_${feedId}`);
});
// 重新加载状态
loadFeedStatuses();
updateStatusFilter();
showRssMessage('已清除所有文章状态', 'success');
// 关闭下拉菜单
document.getElementById('batch-dropdown').classList.remove('active');
}
// 导出状态数据(备份)
function exportStatusData() {
const statusData = {};
const rows = document.querySelectorAll('.rss-feeds-table tbody tr');
rows.forEach(row => {
const feedId = row.getAttribute('data-feed-id');
const status = localStorage.getItem(`feed_status_${feedId}`) || 'unread';
statusData[feedId] = status;
});
const dataStr = JSON.stringify(statusData, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = `rss-status-backup-${new Date().toISOString().slice(0,10)}.json`;
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
showRssMessage('状态数据已导出', 'success');
// 关闭下拉菜单
document.getElementById('batch-dropdown').classList.remove('active');
}
// 导入状态数据(恢复)
function importStatusData() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const statusData = JSON.parse(e.target.result);
let importedCount = 0;
// 应用导入的状态
for (const feedId in statusData) {
if (statusData.hasOwnProperty(feedId)) {
localStorage.setItem(`feed_status_${feedId}`, statusData[feedId]);
importedCount++;
}
}
// 重新加载状态
loadFeedStatuses();
updateStatusFilter();
showRssMessage(`已导入 ${importedCount} 条状态记录`, 'success');
} catch (error) {
showRssMessage('导入失败:文件格式不正确', 'error');
}
};
reader.readAsText(file);
};
input.click();
// 关闭下拉菜单
document.getElementById('batch-dropdown').classList.remove('active');
}
// ==================== 新增:刷新日志相关函数 ====================
// 获取刷新日志
function getRefreshLogs(type, pageSize = 10) {
return fetch(window.UrlNavConfig.actionUrl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'do=getRefreshLogs&type=' + type + '&pageSize=' + pageSize,
credentials: 'include'
}).then(response => response.json());
}
// 获取最近成功的网址24小时内
function getRecentSuccessUrls(pageSize = 10) {
return getRefreshLogs('success', pageSize);
}
// 获取最近失败的网址24小时内
function getRecentFailedUrls(pageSize = 10) {
return getRefreshLogs('failed', pageSize);
}
</script>
<?php
include 'footer.php';
?>