Files
RelatedPosts/Plugin.php

1165 lines
38 KiB
PHP
Raw Normal View History

2026-02-23 19:53:20 +08:00
<?php
/**
* 上一篇下一篇首页&相关推荐
* @package RelatedPosts
* @author 石头厝
* @version 5.6
* @link https://www.shitoucuo.com
*/
class RelatedPosts_Plugin implements Typecho_Plugin_Interface
{
public static function activate()
{
// 只注册footer钩子避免header冲突
Typecho_Plugin::factory('Widget_Archive')->footer = array('RelatedPosts_Plugin', 'footer');
return _t('插件已激活');
}
public static function deactivate()
{
return _t('插件已禁用');
}
public static function config(Typecho_Widget_Helper_Form $form)
{
// 上一篇/下一篇设置
$showNav = new Typecho_Widget_Helper_Form_Element_Radio(
'showNav',
array('1' => _t('显示'), '0' => _t('不显示')),
'1',
_t('是否显示上一篇/下一篇导航')
);
$form->addInput($showNav);
// 相关推荐设置
$showRelated = new Typecho_Widget_Helper_Form_Element_Radio(
'showRelated',
array('1' => _t('显示'), '0' => _t('不显示')),
'1',
_t('是否显示相关推荐')
);
$form->addInput($showRelated);
// 显示数量
$postsNum = new Typecho_Widget_Helper_Form_Element_Text(
'postsNum', NULL, '6',
_t('相关文章显示数量'),
_t('每次显示的相关文章数量')
);
$form->addInput($postsNum->addRule('isInteger', _t('请输入整数')));
// 卡片尺寸
$cardSize = new Typecho_Widget_Helper_Form_Element_Radio(
'cardSize',
array(
'small' => _t('小卡片'),
'medium' => _t('中卡片'),
'large' => _t('大卡片')
),
'small',
_t('相关推荐卡片尺寸')
);
$form->addInput($cardSize);
}
public static function personalConfig(Typecho_Widget_Helper_Form $form){}
/**
* 获取上一篇/下一篇文章
*/
private static function getAdjacentPosts($cid)
{
$db = Typecho_Db::get();
$prevPost = $db->fetchRow($db->select()->from('table.contents')
->where('cid < ?', $cid)
->where('type = ?', 'post')
->where('status = ?', 'publish')
->order('cid', Typecho_Db::SORT_DESC)
->limit(1));
$nextPost = $db->fetchRow($db->select()->from('table.contents')
->where('cid > ?', $cid)
->where('type = ?', 'post')
->where('status = ?', 'publish')
->order('cid', Typecho_Db::SORT_ASC)
->limit(1));
return array(
'prev' => $prevPost,
'next' => $nextPost
);
}
/**
* 获取文章封面 - 增强版:支持各种图片格式和插件排版
*/
private static function getPostThumbnail($postArray)
{
if (!is_array($postArray) || empty($postArray)) {
return '';
}
// 方法1优先检查自定义字段缩略图
$thumbnail = self::getThumbnailFromFields($postArray);
if (!empty($thumbnail)) {
return $thumbnail;
}
$text = isset($postArray['text']) ? $postArray['text'] : '';
// 如果文章内容为空,返回空
if (empty($text)) {
return '';
}
// 方法2使用DOM解析器提取图片最可靠的方法
$thumbnail = self::extractImageWithDom($text);
if (!empty($thumbnail)) {
return $thumbnail;
}
// 方法3使用正则表达式匹配多种格式
$thumbnail = self::extractImageWithRegex($text);
if (!empty($thumbnail)) {
return $thumbnail;
}
// 没有找到图片
return '';
}
/**
* 从自定义字段获取缩略图
*/
private static function getThumbnailFromFields($postArray)
{
if (isset($postArray['fields'])) {
try {
if (is_string($postArray['fields'])) {
$fields = @unserialize($postArray['fields']);
if (is_array($fields)) {
// 尝试常见的缩略图字段名
$thumbnailFields = ['thumb', 'thumbnail', 'cover', 'image', 'featured_image'];
foreach ($thumbnailFields as $field) {
if (!empty($fields[$field])) {
$thumb = trim($fields[$field]);
return self::processImageUrl($thumb);
}
}
// 检查所有字段寻找可能的图片URL
foreach ($fields as $fieldValue) {
if (is_string($fieldValue) && !empty($fieldValue)) {
$fieldValue = trim($fieldValue);
// 检查是否是图片URL
if (preg_match('/\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i', $fieldValue) ||
preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $fieldValue, $matches)) {
$thumb = !empty($matches[1]) ? $matches[1] : $fieldValue;
return self::processImageUrl($thumb);
}
}
}
}
}
} catch (Exception $e) {
// 忽略错误,继续其他方法
}
}
return '';
}
/**
* 使用DOM解析器提取图片最可靠
*/
private static function extractImageWithDom($html)
{
// 检查是否支持DOMDocument
if (!class_exists('DOMDocument')) {
return '';
}
try {
$dom = new DOMDocument();
libxml_use_internal_errors(true); // 抑制HTML解析错误
// 添加HTML包装确保能正确解析片段
$wrappedHtml = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>' . $html . '</body></html>';
$dom->loadHTML($wrappedHtml, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$images = $dom->getElementsByTagName('img');
// 查找第一张合适的图片
foreach ($images as $img) {
// 尝试多个可能的src属性
$src = $img->getAttribute('src');
if (empty($src)) {
$src = $img->getAttribute('data-src'); // 懒加载图片
}
if (empty($src)) {
$src = $img->getAttribute('data-lazy-src'); // WordPress懒加载
}
if (empty($src)) {
$src = $img->getAttribute('data-original'); // 其他懒加载
}
// 跳过base64图片和空src
if (!empty($src) && strpos($src, 'data:image') !== 0) {
// 检查图片尺寸,跳过太小的图标
$width = $img->getAttribute('width');
$height = $img->getAttribute('height');
// 如果不是图标大小,返回它
if ((empty($width) || $width > 50) && (empty($height) || $height > 50)) {
$processedUrl = self::processImageUrl($src);
if (!empty($processedUrl)) {
return $processedUrl;
}
}
}
}
// 如果没有找到合适的img标签检查背景图片
$elements = $dom->getElementsByTagName('*');
foreach ($elements as $element) {
$style = $element->getAttribute('style');
if (preg_match('/background(-image)?\s*:\s*url\(["\']?([^"\'()]+)["\']?\)/i', $style, $matches)) {
if (!empty($matches[2])) {
$processedUrl = self::processImageUrl($matches[2]);
if (!empty($processedUrl)) {
return $processedUrl;
}
}
}
}
} catch (Exception $e) {
// 忽略DOM解析错误
}
return '';
}
/**
* 使用正则表达式提取图片
*/
private static function extractImageWithRegex($text)
{
// 解码HTML实体
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// 定义多个匹配模式(按优先级排序)
$patterns = [
// 标准img标签支持各种属性
'/<img[^>]+(?:src|data-src|data-lazy-src|data-original)=["\']([^"\'>]+)["\'][^>]*>/i',
// Markdown图片格式
'/!\[[^\]]*\]\(([^)]+)\)/i',
// 背景图片
'/background(-image)?:\s*url\(["\']?([^"\'()]+)["\']?\)/i',
// 简化的img标签没有引号
'/<img[^>]+(?:src|data-src|data-lazy-src|data-original)=([^ >]+)[^>]*>/i',
// 直接匹配图片URL最后的手段
'/(https?:\/\/[^\s<>"\']+\.(?:jpg|jpeg|png|gif|webp|bmp|svg)(?:\?[^\s<>"\']*)?)/i'
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $text, $matches)) {
// 确定哪个分组包含URL
$url = null;
for ($i = 1; $i < count($matches); $i++) {
if (!empty($matches[$i]) && strpos($matches[$i], 'http') !== false) {
$url = trim($matches[$i]);
break;
}
}
if (!empty($url)) {
// 清理URL
$url = preg_replace('/["\']$/', '', $url); // 移除末尾的引号
$url = preg_replace('/\?.*$/', '', $url); // 移除查询参数
// 跳过base64和占位符
if (strpos($url, 'data:image') === 0 ||
strpos($url, 'placeholder') !== false ||
strpos($url, 'blank') !== false) {
continue;
}
$processedUrl = self::processImageUrl($url);
if (!empty($processedUrl)) {
return $processedUrl;
}
}
}
}
return '';
}
/**
* 处理图片URL将相对路径转为绝对路径
*/
private static function processImageUrl($url)
{
if (empty($url)) {
return '';
}
$url = trim($url);
// 跳过base64图片
if (strpos($url, 'data:image') === 0) {
return '';
}
// 已经是完整URL或协议相对URL
if (strpos($url, 'http') === 0 || strpos($url, '//') === 0) {
return filter_var($url, FILTER_VALIDATE_URL) ? $url : '';
}
// 相对路径处理
$options = Helper::options();
$siteUrl = rtrim($options->siteUrl, '/');
// 清理URL
$url = ltrim($url, './');
// 如果以斜杠开头,直接拼接
if (strpos($url, '/') === 0) {
return $siteUrl . $url;
}
// 否则添加到站点URL后
return $siteUrl . '/' . $url;
}
/**
* 获取相关文章 - 确保按分类推荐
*/
public static function getRelatedPosts($cid, $limit = 6)
{
$db = Typecho_Db::get();
try {
// 首先获取当前文章的所有分类ID
$currentCats = $db->fetchAll($db->select('mid')
->from('table.relationships')
->where('cid = ?', $cid));
$related = array();
if (!empty($currentCats)) {
$catIds = array();
foreach ($currentCats as $cat) {
$catIds[] = $cat['mid'];
}
// 获取这些分类下的所有文章(不包括当前文章)
$postsInCats = $db->fetchAll($db->select('DISTINCT c.cid')
->from('table.contents AS c')
->join('table.relationships AS r', 'c.cid = r.cid')
->where('c.type = ?', 'post')
->where('c.status = ?', 'publish')
->where('c.cid != ?', $cid)
->where('r.mid IN ?', $catIds)
->order('RAND()'));
// 如果分类下的文章足够,直接随机选取
if (count($postsInCats) >= $limit) {
shuffle($postsInCats);
$selectedCids = array_slice($postsInCats, 0, $limit);
$selectedCids = array_column($selectedCids, 'cid');
// 获取这些文章的完整信息
$related = $db->fetchAll($db->select()
->from('table.contents')
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('cid IN ?', $selectedCids));
}
// 如果分类下的文章不足,先显示分类下的,再补充随机
else {
// 先获取分类下的所有文章
$related = $db->fetchAll($db->select('c.*')
->from('table.contents AS c')
->join('table.relationships AS r', 'c.cid = r.cid')
->where('c.type = ?', 'post')
->where('c.status = ?', 'publish')
->where('c.cid != ?', $cid)
->where('r.mid IN ?', $catIds)
->group('c.cid')
->limit($limit));
// 如果还不够,补充随机文章
if (count($related) < $limit) {
$need = $limit - count($related);
$exclude = array($cid);
foreach ($related as $post) {
$exclude[] = $post['cid'];
}
$random = $db->fetchAll($db->select()
->from('table.contents')
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('cid NOT IN ?', $exclude)
->order('RAND()')
->limit($need));
$related = array_merge($related, $random);
}
}
}
// 如果文章没有分类,直接返回随机文章
else {
$related = $db->fetchAll($db->select()
->from('table.contents')
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('cid != ?', $cid)
->order('RAND()')
->limit($limit));
}
// 确保返回数量正确
if (count($related) > $limit) {
$related = array_slice($related, 0, $limit);
}
} catch (Exception $e) {
// 出错时返回随机文章
$related = $db->fetchAll($db->select()
->from('table.contents')
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('cid != ?', $cid)
->order('RAND()')
->limit($limit));
}
return $related;
}
/**
* 生成文章链接
*/
private static function getPermalink($postArray)
{
if (!is_array($postArray) || empty($postArray)) {
return '#';
}
$options = Helper::options();
$slug = isset($postArray['slug']) ? $postArray['slug'] : '';
if (!empty($slug)) {
return Typecho_Common::url($slug . '.html', $options->index);
} else {
$cid = isset($postArray['cid']) ? $postArray['cid'] : 0;
return Typecho_Common::url('archives/' . $cid . '/', $options->index);
}
}
/**
* 输出文章导航和相关推荐
*/
public static function output()
{
if (!Typecho_Widget::widget('Widget_Archive')->is('single')) {
return;
}
$widget = Typecho_Widget::widget('Widget_Archive');
$options = Helper::options();
$config = $options->plugin('RelatedPosts');
$currentCid = $widget->cid;
// 生成随机种子,确保每次刷新都不同
$randomSeed = isset($_GET['refresh_related']) ? intval($_GET['refresh_related']) : 1;
$html = '<div class="posts-navigation-container">';
// 输出CSS样式内联在HTML中避免FOUC
$html .= '<style>' . self::getStyles() . '</style>';
// 输出上一篇/下一篇导航(如果启用)
if ($config->showNav) {
$adjacent = self::getAdjacentPosts($currentCid);
$html .= '<div class="posts-navigation">';
// 上一篇
if (!empty($adjacent['prev'])) {
$prev = $adjacent['prev'];
$prevPermalink = self::getPermalink($prev);
$prevTitle = isset($prev['title']) ? $prev['title'] : '';
$html .= '<a href="' . $prevPermalink . '" class="nav-prev" title="' . htmlspecialchars($prevTitle) . '">';
$html .= '<div class="nav-content">';
$html .= '<span class="nav-label">上一篇</span>';
$html .= '<span class="nav-title single-line">' . htmlspecialchars(self::truncateText($prevTitle, 20)) . '</span>';
$html .= '</div>';
$html .= '</a>';
} else {
$html .= '<div class="nav-prev disabled">';
$html .= '<div class="nav-content">';
$html .= '<span class="nav-label">上一篇</span>';
$html .= '<span class="nav-title single-line">没有更多了</span>';
$html .= '</div>';
$html .= '</div>';
}
// 首页链接 - 更换为更简单好看的图标
$html .= '<a href="' . $options->index . '/" class="nav-home" title="返回首页">';
$html .= '<span class="nav-home-icon"><i class="iconfont icon-shiliangzhinengduixiang18-01"></i></span>';
$html .= '</a>';
// 下一篇
if (!empty($adjacent['next'])) {
$next = $adjacent['next'];
$nextPermalink = self::getPermalink($next);
$nextTitle = isset($next['title']) ? $next['title'] : '';
$html .= '<a href="' . $nextPermalink . '" class="nav-next" title="' . htmlspecialchars($nextTitle) . '">';
$html .= '<div class="nav-content">';
$html .= '<span class="nav-label">下一篇</span>';
$html .= '<span class="nav-title single-line">' . htmlspecialchars(self::truncateText($nextTitle, 20)) . '</span>';
$html .= '</div>';
$html .= '</a>';
} else {
$html .= '<div class="nav-next disabled">';
$html .= '<div class="nav-content">';
$html .= '<span class="nav-label">下一篇</span>';
$html .= '<span class="nav-title single-line">没有更多了</span>';
$html .= '</div>';
$html .= '</div>';
}
$html .= '</div>';
}
// 输出相关推荐(如果启用)
if ($config->showRelated) {
$relatedPosts = self::getRelatedPosts($currentCid, $config->postsNum);
if (!empty($relatedPosts)) {
$cardSize = isset($config->cardSize) ? $config->cardSize : 'small';
$html .= '<div id="related-posts-container" class="related-posts-section card-size-' . $cardSize . '">';
$html .= '<div class="related-header">';
$html .= '<h3 class="related-title">相关推荐</h3>';
// 使用随机种子确保每次刷新都不同
$html .= '<a href="?refresh_related=' . ($randomSeed + 1) . '#related-posts-container" class="refresh-btn">';
$html .= '<span class="refresh-icon">⟳</span> 换一批';
$html .= '</a>';
$html .= '</div>';
$html .= '<div class="related-posts-grid">';
foreach ($relatedPosts as $post) {
$html .= self::getPostCardHtml($post);
}
$html .= '</div>';
$html .= '</div>';
}
}
$html .= '</div>';
echo $html;
}
/**
* 生成文章卡片HTML
*/
private static function getPostCardHtml($postArray)
{
if (!is_array($postArray) || empty($postArray)) {
return '';
}
$title = isset($postArray['title']) ? $postArray['title'] : '无标题';
$created = isset($postArray['created']) ? $postArray['created'] : time();
$commentsNum = isset($postArray['commentsNum']) ? $postArray['commentsNum'] : 0;
$permalink = self::getPermalink($postArray);
// 获取封面图 - 使用增强的方法
$thumbnail = self::getPostThumbnail($postArray);
$html = '<div class="related-post-card">';
if (!empty($thumbnail)) {
$html .= '<div class="post-thumb">';
$html .= '<a href="' . $permalink . '" title="' . htmlspecialchars($title) . '">';
$html .= '<img src="' . htmlspecialchars($thumbnail) . '" alt="' . htmlspecialchars($title) . '" loading="lazy" onerror="this.style.display=\'none\';this.parentNode.parentNode.className=\'post-thumb no-thumb\';">';
// 日期标签 - 添加透明度
$html .= '<div class="post-date-overlay">' . date('m-d', $created) . '</div>';
if ($commentsNum > 0) {
// 评论标签 - 添加透明度
$html .= '<div class="post-comments-overlay">' . $commentsNum . '评</div>';
}
$html .= '</a>';
$html .= '</div>';
} else {
$html .= '<div class="post-thumb no-thumb">';
$html .= '<a href="' . $permalink . '" title="' . htmlspecialchars($title) . '">';
$html .= '<div class="no-thumb-placeholder">📄</div>';
// 日期标签 - 添加透明度
$html .= '<div class="post-date-overlay">' . date('m-d', $created) . '</div>';
if ($commentsNum > 0) {
// 评论标签 - 添加透明度
$html .= '<div class="post-comments-overlay">' . $commentsNum . '评</div>';
}
$html .= '</a>';
$html .= '</div>';
}
$html .= '<div class="post-content">';
$html .= '<h4 class="post-title"><a href="' . $permalink . '">' . htmlspecialchars(self::truncateText($title, 28)) . '</a></h4>';
$html .= '</div>';
$html .= '</div>';
return $html;
}
/**
* 截断文本
*/
private static function truncateText($text, $length = 20)
{
if (mb_strlen($text, 'UTF-8') > $length) {
return mb_substr($text, 0, $length, 'UTF-8') . '...';
}
return $text;
}
/**
* 输出CSS
*/
public static function footer()
{
// 保留footer方法但不输出任何内容因为CSS已经内联了
// 这个方法只是为了保持向后兼容性
}
/**
* 获取CSS样式
*/
private static function getStyles()
{
// 返回您原始的CSS完全不变
return '
/* 上一篇下一篇标题只显示一行 */
.posts-navigation .nav-title {
font-size: 15px;
font-weight: 500;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.posts-navigation .nav-title.single-line {
-webkit-line-clamp: 1;
}
/* 导航和相关推荐容器 */
.posts-navigation-container {
margin: 30px 0 0px;
max-width: 100%;
}
/* 上一篇/下一篇导航 */
.posts-navigation {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 20px;
border: 1px solid #dc2626;
border-bottom: 0px solid #dc2626;
border-top: 0px solid #dc2626;
}
.posts-navigation .nav-prev,
.posts-navigation .nav-next {
flex: 1;
display: flex;
align-items: center;
padding: 15px;
background: #fff;
border-radius: 8px;
text-decoration: none;
color: #333;
border: 1px solid #e1e5e9;
transition: all 0.3s ease;
min-height: 70px;
}
.posts-navigation .nav-prev:hover,
.posts-navigation .nav-next:hover {
transform: translateY(-2px);
border-color: #1e87f0;
color: #1e87f0;
}
.posts-navigation .nav-prev.disabled,
.posts-navigation .nav-next.disabled {
background: #f8f9fa;
color: #999;
cursor: not-allowed;
opacity: 0.7;
}
.posts-navigation .nav-content {
flex: 1;
display: flex;
flex-direction: column;
}
.posts-navigation .nav-label {
font-size: 12px;
color:rgb(156 163 175 / var(--tw-text-opacity));
margin-bottom: 4px;
}
/* 首页链接 - 更换为更简单大气的图标 */
.posts-navigation .nav-home {
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #1e87f0 0%, #0d6efd 100%);
border-radius: 50%;
text-decoration: none;
color: white;
border: 1px solid #1e87f0;
transition: all 0.3s ease;
font-size: 20px;
}
.posts-navigation .nav-home:hover {
background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%);
transform: scale(1.1);
border-color: #0d6efd;
}
/* 相关推荐部分 */
.related-posts-section {
background: #f8f9fa;
border-radius: 20px;
padding: 25px;
border: 1px solid #dc2626;
border-bottom: 0px solid #dc2626;
border-top: 0px solid #dc2626;
}
.related-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 2px solid #e8e8e8;
}
.related-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #333;
position: relative;
padding-left: 0; /* 移除左边的内边距 */
}
/* 移除相关推荐标题前的竖线 */
.related-title:before {
display: none; /* 隐藏竖线 */
}
.refresh-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 20px;
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
color: #666;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
text-decoration: none;
}
.refresh-btn:hover {
background: #1e87f0;
color: #fff;
transform: translateY(-1px);
box-shadow: 0 3px 10px rgba(30, 135, 240, 0.2);
border-color: #1e87f0;
}
.refresh-icon {
font-size: 16px;
font-weight: bold;
display: inline-block;
animation: spin 1.5s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.refresh-btn:hover .refresh-icon {
animation: spin 0.8s linear infinite;
}
/* 相关文章网格 */
.related-posts-grid {
display: grid;
gap: 20px;
}
/* 小卡片样式 */
.card-size-small .related-posts-grid {
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
.card-size-small .related-post-card {
background: #fff;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid #eee;
display: flex;
flex-direction: column;
position: relative;
}
.card-size-small .post-thumb {
width: 100%;
height: 120px;
overflow: hidden;
background: #f5f5f5;
position: relative;
}
.card-size-small .post-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.6s ease;
}
/* 中卡片样式 */
.card-size-medium .related-posts-grid {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.card-size-medium .related-post-card {
background: #fff;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid #eee;
display: flex;
flex-direction: column;
position: relative;
}
.card-size-medium .post-thumb {
width: 100%;
height: 160px;
overflow: hidden;
background: #f5f5f5;
position: relative;
}
.card-size-medium .post-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.6s ease;
}
/* 大卡片样式 */
.card-size-large .related-posts-grid {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
}
.card-size-large .related-post-card {
background: #fff;
border-radius: 10px;
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid #eee;
display: flex;
flex-direction: column;
position: relative;
}
.card-size-large .post-thumb {
width: 100%;
height: 180px;
overflow: hidden;
background: #f5f5f5;
position: relative;
}
.card-size-large .post-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.6s ease;
}
/* 通用卡片悬停效果 */
.related-post-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
border-color: #1e87f0;
}
.related-post-card:hover .post-thumb img {
transform: scale(1.08);
}
/* 封面图片上的标签 - 添加透明度 */
.post-date-overlay {
position: absolute;
top: 8px;
left: 8px;
background: #f15a22;
opacity:0.5;
color: white;
font-size: 12px;
padding: 3px 8px;
border-radius: 3px;
font-weight: 500;
}
.post-comments-overlay {
position: absolute;
top: 8px;
right: 8px;
background:#f15a22;
color: white;
opacity:0.5;
font-size: 12px;
padding: 3px 8px;
border-radius: 3px;
font-weight: 500;
}
/* 无缩略图样式 */
.post-thumb.no-thumb {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.no-thumb-placeholder {
font-size: 32px;
opacity: 0.7;
color: white;
}
/* 内容区域 */
.post-content {
padding: 15px;
flex: 1;
display: flex;
flex-direction: column;
}
.post-title {
margin: 0;
font-size: 14px;
line-height: 1.4;
font-weight: 500;
}
.card-size-medium .post-title {
font-size: 16px;
}
.card-size-large .post-title {
font-size: 17px;
}
.post-title a {
color: #333;
text-decoration: none;
transition: color 0.3s;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.post-title a:hover {
color: #1e87f0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.posts-navigation {
flex-direction: column;
gap: 15px;
}
.posts-navigation .nav-prev,
.posts-navigation .nav-next {
width: 100%;
}
.posts-navigation .nav-home {
order: 3;
width: 100%;
height: auto;
border-radius: 8px;
padding: 12px;
}
.related-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.refresh-btn {
align-self: flex-start;
}
.card-size-large .related-posts-grid,
.card-size-medium .related-posts-grid,
.card-size-small .related-posts-grid {
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
}
@media (max-width: 480px) {
.related-posts-section {
padding: 20px;
}
.card-size-large .related-posts-grid,
.card-size-medium .related-posts-grid,
.card-size-small .related-posts-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.posts-navigation {
padding: 15px;
}
}
/* 夜间模式支持 */
.dark .posts-navigation {
background: #1d1d1e;
border: 1px solid #dc2626;
border-bottom: 0px solid #dc2626;
border-top: 0px solid #dc2626;
}
.dark .posts-navigation .nav-prev,
.dark .posts-navigation .nav-next,
.dark .posts-navigation .nav-home {
background: rgb(10 12 25 / 1);
border-color: #333;
color:rgb(156 163 175 / var(--tw-text-opacity));
}
.dark .posts-navigation .nav-prev.disabled,
.dark .posts-navigation .nav-next.disabled {
background: #4a5568;
color: #a0aec0;
}
.dark .posts-navigation .nav-home {
background: rgb(10 12 25 / 1);
border-color: #333;
}
.dark .related-posts-section {
background: #1d1d1e;
border: 1px solid #dc2626;
border-bottom: 0px solid #dc2626;
border-top: 0px solid #dc2626;
}
.dark .related-header {
border-color: #333;
}
.dark .related-title {
color: rgb(156 163 175 / var(--tw-text-opacity));
}
.dark .refresh-btn {
background: rgb(10 12 25 / 1);
color:rgb(156 163 175 / var(--tw-text-opacity));
border-color: #333;
}
.dark .related-post-card {
background: rgb(10 12 25 / 1);
border-color: #333;
}
.dark .post-title a {
color: rgb(156 163 175 / var(--tw-text-opacity));
}
.dark .post-title a:hover {
color: #f15a22;
}
.dark .post-thumb {
background: #718096;
}
.dark .post-thumb.no-thumb {
background: linear-gradient(135deg, #4c51bf 0%, #805ad5 100%);
}
.dark .post-date-overlay {
background:#f15a22;
color: #fff;
}
.dark .post-comments-overlay {
background: #f15a22;
color:#fff;
}
';
}
}