1165 lines
38 KiB
PHP
1165 lines
38 KiB
PHP
|
|
<?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;
|
|||
|
|
}
|
|||
|
|
';
|
|||
|
|
}
|
|||
|
|
}
|