Files
RelatedPosts/Plugin.php
2026-02-23 19:53:20 +08:00

1165 lines
38 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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;
}
';
}
}