2527 lines
105 KiB
PHP
2527 lines
105 KiB
PHP
|
|
<?php
|
|||
|
|
/**
|
|||
|
|
* TMDB电影
|
|||
|
|
*
|
|||
|
|
* @package MovieInfo
|
|||
|
|
* @author 石头厝
|
|||
|
|
* @version 1.0.0
|
|||
|
|
* @link https://www.shitoucuo.com/
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
|
|||
|
|
|
|||
|
|
class MovieInfo_Plugin implements Typecho_Plugin_Interface
|
|||
|
|
{
|
|||
|
|
/**
|
|||
|
|
* 激活插件
|
|||
|
|
*/
|
|||
|
|
public static function activate()
|
|||
|
|
{
|
|||
|
|
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('MovieInfo_Plugin', 'parse');
|
|||
|
|
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('MovieInfo_Plugin', 'parse');
|
|||
|
|
Typecho_Plugin::factory('admin/write-post.php')->bottom = array('MovieInfo_Plugin', 'renderButton');
|
|||
|
|
Typecho_Plugin::factory('admin/write-page.php')->bottom = array('MovieInfo_Plugin', 'renderButton');
|
|||
|
|
|
|||
|
|
$cacheDir = dirname(__FILE__) . '/cache/';
|
|||
|
|
if (!file_exists($cacheDir)) mkdir($cacheDir, 0755, true);
|
|||
|
|
|
|||
|
|
return 'TMDB电影插件激活成功!';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 禁用插件
|
|||
|
|
*/
|
|||
|
|
public static function deactivate()
|
|||
|
|
{
|
|||
|
|
return '插件已禁用';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 配置面板
|
|||
|
|
*/
|
|||
|
|
public static function config(Typecho_Widget_Helper_Form $form)
|
|||
|
|
{
|
|||
|
|
// TMDB API 配置
|
|||
|
|
$tmdbApiKey = new Typecho_Widget_Helper_Form_Element_Text('tmdbApiKey', NULL, '',
|
|||
|
|
'TMDB API Key', '在TMDB网站申请的API密钥');
|
|||
|
|
$form->addInput($tmdbApiKey);
|
|||
|
|
|
|||
|
|
$language = new Typecho_Widget_Helper_Form_Element_Select('language',
|
|||
|
|
array(
|
|||
|
|
'zh-CN' => '中文',
|
|||
|
|
'en-US' => '英文',
|
|||
|
|
'ja-JP' => '日文',
|
|||
|
|
'ko-KR' => '韩文',
|
|||
|
|
'fr-FR' => '法文'
|
|||
|
|
), 'zh-CN', '显示语言', '电影信息的显示语言');
|
|||
|
|
$form->addInput($language);
|
|||
|
|
|
|||
|
|
$cacheEnable = new Typecho_Widget_Helper_Form_Element_Radio('cacheEnable',
|
|||
|
|
array('1' => '启用', '0' => '禁用'),
|
|||
|
|
'1', '启用缓存', '缓存电影信息,提升访问速度');
|
|||
|
|
$form->addInput($cacheEnable);
|
|||
|
|
|
|||
|
|
$cacheTime = new Typecho_Widget_Helper_Form_Element_Text('cacheTime', NULL, '30',
|
|||
|
|
'缓存时间(天)', '电影信息缓存保留天数');
|
|||
|
|
$cacheTime->addRule('isInteger', '请输入整数');
|
|||
|
|
$form->addInput($cacheTime);
|
|||
|
|
|
|||
|
|
$imageProxy = new Typecho_Widget_Helper_Form_Element_Text('imageProxy', NULL,
|
|||
|
|
'https://images.weserv.nl/?url=', '图片代理', '用于加载TMDB图片');
|
|||
|
|
$form->addInput($imageProxy);
|
|||
|
|
|
|||
|
|
$defaultPoster = new Typecho_Widget_Helper_Form_Element_Text('defaultPoster', NULL,
|
|||
|
|
'https://via.placeholder.com/300x450/CCCCCC/666666?text=No+Poster',
|
|||
|
|
'默认海报', '当无法获取海报时显示的图片');
|
|||
|
|
$form->addInput($defaultPoster);
|
|||
|
|
|
|||
|
|
$summaryLength = new Typecho_Widget_Helper_Form_Element_Text('summaryLength', NULL, '200',
|
|||
|
|
'简介显示长度', '简介默认显示的最大字符数,超出部分可展开查看');
|
|||
|
|
$summaryLength->addRule('isInteger', '请输入整数');
|
|||
|
|
$form->addInput($summaryLength);
|
|||
|
|
|
|||
|
|
$expandText = new Typecho_Widget_Helper_Form_Element_Text('expandText', NULL, '展开',
|
|||
|
|
'"展开"文字', '点击展开完整简介的文字');
|
|||
|
|
$form->addInput($expandText);
|
|||
|
|
|
|||
|
|
$collapseText = new Typecho_Widget_Helper_Form_Element_Text('collapseText', NULL, '收起',
|
|||
|
|
'"收起"文字', '点击收起简介的文字');
|
|||
|
|
$form->addInput($collapseText);
|
|||
|
|
|
|||
|
|
$expandColor = new Typecho_Widget_Helper_Form_Element_Text('expandColor', NULL, '#0073aa',
|
|||
|
|
'展开按钮颜色', '展开/收起按钮的文字颜色');
|
|||
|
|
$form->addInput($expandColor);
|
|||
|
|
|
|||
|
|
// 独立页面每页显示条数设置
|
|||
|
|
$pageSize = new Typecho_Widget_Helper_Form_Element_Text('pageSize', NULL, '10',
|
|||
|
|
'独立页面每页显示条数', '在独立页面中每页显示的电影数量(1-50)');
|
|||
|
|
$pageSize->addRule('isInteger', '请输入整数');
|
|||
|
|
$pageSize->addRule(array(new MovieInfo_Plugin, 'validatePageSize'), '请输入1-50之间的整数');
|
|||
|
|
$form->addInput($pageSize);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证页面显示条数
|
|||
|
|
*/
|
|||
|
|
public static function validatePageSize($value)
|
|||
|
|
{
|
|||
|
|
$value = intval($value);
|
|||
|
|
if ($value < 1 || $value > 50) {
|
|||
|
|
throw new Typecho_Widget_Exception('请输入1-50之间的整数');
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 个人配置面板
|
|||
|
|
*/
|
|||
|
|
public static function personalConfig(Typecho_Widget_Helper_Form $form)
|
|||
|
|
{
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析短代码
|
|||
|
|
*/
|
|||
|
|
public static function parse($content, $widget, $lastResult)
|
|||
|
|
{
|
|||
|
|
$content = empty($lastResult) ? $content : $lastResult;
|
|||
|
|
|
|||
|
|
// 如果是独立页面,且内容中包含[all_movies]标记,则显示所有电影
|
|||
|
|
if ($widget instanceof Widget_Archive && $widget->is('page')) {
|
|||
|
|
$pattern = '/\[all_movies(?::page=(\d+))?\]/i';
|
|||
|
|
if (preg_match_all($pattern, $content, $matches)) {
|
|||
|
|
foreach ($matches[0] as $key => $match) {
|
|||
|
|
$page = isset($matches[1][$key]) ? intval($matches[1][$key]) : 1;
|
|||
|
|
$allMoviesHtml = self::renderAllMovies($page);
|
|||
|
|
$content = str_replace($match, $allMoviesHtml, $content);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果是单篇文章,解析电影短代码
|
|||
|
|
if ($widget instanceof Widget_Archive && $widget->is('single')) {
|
|||
|
|
// 匹配 [movie:数字] 或 [movie:数字:短评] 格式
|
|||
|
|
$pattern = '/\[movie:(\d+)(?::([^\]]+))?\]/i';
|
|||
|
|
if (preg_match_all($pattern, $content, $matches)) {
|
|||
|
|
foreach ($matches[0] as $key => $match) {
|
|||
|
|
$movieId = $matches[1][$key];
|
|||
|
|
$reviewWithCustom = isset($matches[2][$key]) ? trim($matches[2][$key]) : '';
|
|||
|
|
|
|||
|
|
$review = '';
|
|||
|
|
$customData = array();
|
|||
|
|
|
|||
|
|
// 解码短评和自定义数据
|
|||
|
|
if (!empty($reviewWithCustom)) {
|
|||
|
|
// 分离短评和自定义数据
|
|||
|
|
if (strpos($reviewWithCustom, '|CUSTOM:') !== false) {
|
|||
|
|
list($review, $customJson) = explode('|CUSTOM:', $reviewWithCustom, 2);
|
|||
|
|
|
|||
|
|
// 解码自定义数据
|
|||
|
|
if (!empty($customJson)) {
|
|||
|
|
$decodedJson = urldecode($customJson);
|
|||
|
|
$customData = json_decode($decodedJson, true);
|
|||
|
|
if (!is_array($customData)) {
|
|||
|
|
$customData = array();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 检查旧格式的自定义数据
|
|||
|
|
if (preg_match('/^(自定义:(.*))$/u', $reviewWithCustom, $customMatch)) {
|
|||
|
|
$review = '';
|
|||
|
|
$customData = self::parseLegacyCustomData($customMatch[1]);
|
|||
|
|
} else {
|
|||
|
|
// 检查中文括号格式
|
|||
|
|
if (preg_match('/^(.*?)(自定义:(.*))$/u', $reviewWithCustom, $customMatch)) {
|
|||
|
|
$review = trim($customMatch[1]);
|
|||
|
|
$customData = self::parseLegacyCustomData($customMatch[2]);
|
|||
|
|
} else {
|
|||
|
|
$review = $reviewWithCustom;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 解码短评
|
|||
|
|
if (!empty($review)) {
|
|||
|
|
$review = str_replace(array('[', ']'), array('[', ']'), $review);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取电影数据
|
|||
|
|
$movieHtml = self::renderMovie($movieId, $review, $customData);
|
|||
|
|
$content = str_replace($match, $movieHtml, $content);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $content;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析旧格式的自定义数据
|
|||
|
|
*/
|
|||
|
|
private static function parseLegacyCustomData($customText)
|
|||
|
|
{
|
|||
|
|
$customData = array();
|
|||
|
|
|
|||
|
|
$pairs = explode(',', $customText);
|
|||
|
|
foreach ($pairs as $pair) {
|
|||
|
|
if (strpos($pair, ':') !== false) {
|
|||
|
|
list($key, $value) = explode(':', $pair, 2);
|
|||
|
|
switch (trim($key)) {
|
|||
|
|
case '开始观看':
|
|||
|
|
$customData['startDate'] = trim($value);
|
|||
|
|
break;
|
|||
|
|
case '结束观看':
|
|||
|
|
$customData['watchDate'] = trim($value);
|
|||
|
|
break;
|
|||
|
|
case '观看方式':
|
|||
|
|
$customData['watchMethod'] = trim($value);
|
|||
|
|
break;
|
|||
|
|
case '电影分类':
|
|||
|
|
$customData['movieCategory'] = trim($value);
|
|||
|
|
break;
|
|||
|
|
case '推荐指数':
|
|||
|
|
$starCount = substr_count($value, '★');
|
|||
|
|
$customData['recommendation'] = $starCount;
|
|||
|
|
break;
|
|||
|
|
case '我的评分':
|
|||
|
|
$customData['myRating'] = trim($value);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $customData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 渲染单部电影信息
|
|||
|
|
*/
|
|||
|
|
private static function renderMovie($movieId, $review = '', $customData = array())
|
|||
|
|
{
|
|||
|
|
// 获取电影数据
|
|||
|
|
$movieData = self::getMovieData($movieId, $review, $customData);
|
|||
|
|
|
|||
|
|
if (!$movieData || empty($movieData['title'])) {
|
|||
|
|
return '<div style="padding:10px; background:#f8d7da; color:#721c24; border:1px solid #f5c6cb; border-radius:4px; margin:10px 0;">
|
|||
|
|
获取电影信息失败,ID:' . htmlspecialchars($movieId) . '
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options')->plugin('MovieInfo');
|
|||
|
|
$imageProxy = isset($options->imageProxy) ? $options->imageProxy : 'https://images.weserv.nl/?url=';
|
|||
|
|
$defaultPoster = isset($options->defaultPoster) ? $options->defaultPoster :
|
|||
|
|
'https://via.placeholder.com/300x450/CCCCCC/666666?text=No+Poster';
|
|||
|
|
$summaryLength = isset($options->summaryLength) ? intval($options->summaryLength) : 200;
|
|||
|
|
$expandText = isset($options->expandText) ? $options->expandText : '展开';
|
|||
|
|
$collapseText = isset($options->collapseText) ? $options->collapseText : '收起';
|
|||
|
|
$expandColor = isset($options->expandColor) ? $options->expandColor : '#0073aa';
|
|||
|
|
|
|||
|
|
$title = htmlspecialchars($movieData['title']);
|
|||
|
|
$originalTitle = isset($movieData['original_title']) ? htmlspecialchars($movieData['original_title']) : '';
|
|||
|
|
$releaseYear = isset($movieData['release_year']) ? htmlspecialchars($movieData['release_year']) : '未知';
|
|||
|
|
$director = isset($movieData['director']) ? htmlspecialchars($movieData['director']) : '未知';
|
|||
|
|
$genres = isset($movieData['genres']) ? $movieData['genres'] : array('未知');
|
|||
|
|
$runtime = isset($movieData['runtime']) ? htmlspecialchars($movieData['runtime']) . '分钟' : '未知';
|
|||
|
|
$summary = isset($movieData['overview']) ? $movieData['overview'] : '';
|
|||
|
|
$rating = isset($movieData['vote_average']) ? floatval($movieData['vote_average']) : 0;
|
|||
|
|
$ratingCount = isset($movieData['vote_count']) ? intval($movieData['vote_count']) : 0;
|
|||
|
|
$productionCountries = isset($movieData['production_countries']) ? $movieData['production_countries'] : array('未知');
|
|||
|
|
|
|||
|
|
$review = htmlspecialchars($review);
|
|||
|
|
|
|||
|
|
// 自定义字段
|
|||
|
|
$startDate = isset($customData['startDate']) ? htmlspecialchars($customData['startDate']) : '';
|
|||
|
|
$watchDate = isset($customData['watchDate']) ? htmlspecialchars($customData['watchDate']) : '';
|
|||
|
|
$watchMethod = isset($customData['watchMethod']) ? htmlspecialchars($customData['watchMethod']) : '';
|
|||
|
|
$movieCategory = isset($customData['movieCategory']) ? htmlspecialchars($customData['movieCategory']) : '';
|
|||
|
|
$recommendation = isset($customData['recommendation']) ? intval($customData['recommendation']) : 0;
|
|||
|
|
$myRating = isset($customData['myRating']) ? htmlspecialchars($customData['myRating']) : '';
|
|||
|
|
|
|||
|
|
// 如果customData中没有,尝试从movieData中获取
|
|||
|
|
if (empty($startDate) && isset($movieData['custom_start_date']) && !empty($movieData['custom_start_date'])) {
|
|||
|
|
$startDate = htmlspecialchars($movieData['custom_start_date']);
|
|||
|
|
}
|
|||
|
|
if (empty($watchDate) && isset($movieData['custom_watch_date']) && !empty($movieData['custom_watch_date'])) {
|
|||
|
|
$watchDate = htmlspecialchars($movieData['custom_watch_date']);
|
|||
|
|
}
|
|||
|
|
if (empty($watchMethod) && isset($movieData['custom_watch_method']) && !empty($movieData['custom_watch_method'])) {
|
|||
|
|
$watchMethod = htmlspecialchars($movieData['custom_watch_method']);
|
|||
|
|
}
|
|||
|
|
if (empty($movieCategory) && isset($movieData['custom_movie_category']) && !empty($movieData['custom_movie_category'])) {
|
|||
|
|
$movieCategory = htmlspecialchars($movieData['custom_movie_category']);
|
|||
|
|
}
|
|||
|
|
if ($recommendation == 0 && isset($movieData['custom_recommendation'])) {
|
|||
|
|
$recommendation = intval($movieData['custom_recommendation']);
|
|||
|
|
}
|
|||
|
|
if (empty($myRating) && isset($movieData['custom_my_rating'])) {
|
|||
|
|
$myRating = htmlspecialchars($movieData['custom_my_rating']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查简介长度
|
|||
|
|
$isSummaryLong = false;
|
|||
|
|
$summaryShort = $summary;
|
|||
|
|
|
|||
|
|
$plainSummary = strip_tags($summary);
|
|||
|
|
if (mb_strlen($plainSummary, 'UTF-8') > $summaryLength) {
|
|||
|
|
$isSummaryLong = true;
|
|||
|
|
$plainShort = mb_substr($plainSummary, 0, $summaryLength, 'UTF-8');
|
|||
|
|
$summaryShort = $plainShort . '...';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 海报URL
|
|||
|
|
$posterPath = isset($movieData['poster_path']) ? $movieData['poster_path'] : '';
|
|||
|
|
if ($posterPath) {
|
|||
|
|
$posterUrl = 'https://image.tmdb.org/t/p/w300' . $posterPath;
|
|||
|
|
$posterSrc = $imageProxy . urlencode($posterUrl);
|
|||
|
|
} else {
|
|||
|
|
$posterSrc = $defaultPoster;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 类型显示 - 确保不换行
|
|||
|
|
$genresHtml = '';
|
|||
|
|
if (is_array($genres)) {
|
|||
|
|
$genresHtml = implode(' / ', $genres);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 地区显示
|
|||
|
|
$countriesHtml = '';
|
|||
|
|
if (is_array($productionCountries)) {
|
|||
|
|
$countriesHtml = implode(' / ', $productionCountries);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TMDB评分显示 - 移除括号和评价人数
|
|||
|
|
$ratingHtml = '';
|
|||
|
|
if ($rating > 0) {
|
|||
|
|
$ratingHtml = '<div class="movie-rating" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
|
|||
|
|
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">评分:</span>
|
|||
|
|
<span style="color:var(--movie-rating-color); font-weight:600; font-size:16px;">' . number_format($rating, 1) . '</span>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 我的评分显示
|
|||
|
|
$myRatingHtml = '';
|
|||
|
|
if (!empty($myRating)) {
|
|||
|
|
$myRatingHtml = '<div class="movie-my-rating" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
|
|||
|
|
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">我的评分:</span>
|
|||
|
|
<span style="color:var(--movie-my-rating-color); font-weight:600; font-size:16px;">' . $myRating . '</span>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 推荐指数星星
|
|||
|
|
$recommendationHtml = '';
|
|||
|
|
if ($recommendation > 0) {
|
|||
|
|
$recommendationHtml = '<div class="movie-recommendation" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
|
|||
|
|
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">推荐指数:</span>';
|
|||
|
|
|
|||
|
|
for ($i = 1; $i <= 5; $i++) {
|
|||
|
|
if ($i <= $recommendation) {
|
|||
|
|
$recommendationHtml .= '<span style="color:var(--movie-star-color); font-size:16px; margin-right:2px;">★</span>';
|
|||
|
|
} else {
|
|||
|
|
$recommendationHtml .= '<span style="color:var(--movie-star-empty-color); font-size:16px; margin-right:2px;">★</span>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
$recommendationHtml .= '</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 自定义信息 - 按图书插件逻辑处理开始和结束观看
|
|||
|
|
$customInfoHtml = '';
|
|||
|
|
$hasCustomInfo = false;
|
|||
|
|
|
|||
|
|
// 判断开始和结束观看日期是否相同
|
|||
|
|
$hasStartDate = !empty($startDate);
|
|||
|
|
$hasWatchDate = !empty($watchDate);
|
|||
|
|
$datesEqual = $hasStartDate && $hasWatchDate && $startDate === $watchDate;
|
|||
|
|
|
|||
|
|
if ($hasStartDate || $hasWatchDate || $watchMethod || $movieCategory) {
|
|||
|
|
$hasCustomInfo = true;
|
|||
|
|
|
|||
|
|
if ($datesEqual) {
|
|||
|
|
// 开始和结束观看日期相同,显示"观看日期"
|
|||
|
|
$customInfoHtml .= '<div class="movie-watch-date" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
|
|||
|
|
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">观看日期:</span>
|
|||
|
|
<span style="color:var(--movie-text-primary);">' . $startDate . '</span>
|
|||
|
|
</div>';
|
|||
|
|
} else {
|
|||
|
|
// 日期不同或只有一个日期,分开显示
|
|||
|
|
if ($startDate) {
|
|||
|
|
$customInfoHtml .= '<div class="movie-start-date" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
|
|||
|
|
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">开始观看:</span>
|
|||
|
|
<span style="color:var(--movie-text-primary);">' . $startDate . '</span>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($watchDate) {
|
|||
|
|
$customInfoHtml .= '<div class="movie-end-date" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
|
|||
|
|
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">结束观看:</span>
|
|||
|
|
<span style="color:var(--movie-text-primary);">' . $watchDate . '</span>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($watchMethod) {
|
|||
|
|
$customInfoHtml .= '<div class="movie-watch-method" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
|
|||
|
|
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">观看方式:</span>
|
|||
|
|
<span style="color:var(--movie-text-primary);">' . $watchMethod . '</span>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($movieCategory) {
|
|||
|
|
$customInfoHtml .= '<div class="movie-category" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
|
|||
|
|
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">电影分类:</span>
|
|||
|
|
<span style="color:var(--movie-text-primary);">' . $movieCategory . '</span>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 右侧栏完整HTML
|
|||
|
|
$rightColumnHtml = '';
|
|||
|
|
|
|||
|
|
if ($myRatingHtml) {
|
|||
|
|
$rightColumnHtml .= $myRatingHtml;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($recommendationHtml) {
|
|||
|
|
$rightColumnHtml .= $recommendationHtml;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($customInfoHtml) {
|
|||
|
|
$rightColumnHtml .= $customInfoHtml;
|
|||
|
|
} else {
|
|||
|
|
$rightColumnHtml .= '<div class="movie-no-record" style="margin:0 0 10px 0; font-size:14px; color:var(--movie-text-secondary);">
|
|||
|
|
<span style="font-weight:600; color:var(--movie-text-primary); display:inline-block; width:70px;">观看记录:</span>
|
|||
|
|
<span style="color:var(--movie-text-tertiary);">暂无记录</span>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 简介部分HTML
|
|||
|
|
$summaryHtml = '<div class="movie-summary-container">';
|
|||
|
|
|
|||
|
|
if ($isSummaryLong) {
|
|||
|
|
$summaryHtml .= '
|
|||
|
|
<div class="movie-summary-short" style="display:block;">
|
|||
|
|
<div style="font-size:14px; line-height:1.8; color:var(--movie-text-primary); text-align:justify; font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
|
|||
|
|
' . nl2br($summaryShort) . '
|
|||
|
|
<a href="javascript:void(0);" class="movie-summary-expand"
|
|||
|
|
onclick="toggleMovieSummary(this)"
|
|||
|
|
style="color:' . htmlspecialchars($expandColor) . '; text-decoration:none; font-weight:600; cursor:pointer; margin-left:5px; padding:2px 8px; background:var(--movie-button-bg); border-radius:4px; border:1px solid var(--movie-border-color); font-size:13px; transition:all 0.2s ease;"
|
|||
|
|
onmouseover="this.style.backgroundColor=\'var(--movie-button-hover-bg)\'; this.style.borderColor=\'' . htmlspecialchars($expandColor) . '\';"
|
|||
|
|
onmouseout="this.style.backgroundColor=\'var(--movie-button-bg)\'; this.style.borderColor=\'var(--movie-border-color)\';">'
|
|||
|
|
. htmlspecialchars($expandText) . ' ↓
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="movie-summary-full" style="display:none;">
|
|||
|
|
<div style="font-size:14px; line-height:1.8; color:var(--movie-text-primary); text-align:justify; font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
|
|||
|
|
' . $summary . '
|
|||
|
|
<a href="javascript:void(0);" class="movie-summary-collapse"
|
|||
|
|
onclick="toggleMovieSummary(this)"
|
|||
|
|
style="color:' . htmlspecialchars($expandColor) . '; text-decoration:none; font-weight:600; cursor:pointer; margin-left:5px; padding:2px 8px; background:var(--movie-button-bg); border-radius:4px; border:1px solid var(--movie-border-color); font-size:13px; transition:all 0.2s ease;"
|
|||
|
|
onmouseover="this.style.backgroundColor=\'var(--movie-button-hover-bg)\'; this.style.borderColor=\'' . htmlspecialchars($expandColor) . '\';"
|
|||
|
|
onmouseout="this.style.backgroundColor=\'var(--movie-button-bg)\'; this.style.borderColor=\'var(--movie-border-color)\';">'
|
|||
|
|
. htmlspecialchars($collapseText) . ' ↑
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
</div>';
|
|||
|
|
} else {
|
|||
|
|
$summaryHtml .= '
|
|||
|
|
<div style="font-size:14px; line-height:1.8; color:var(--movie-text-primary); text-align:justify; font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
|
|||
|
|
' . $summary . '
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$summaryHtml .= '</div>';
|
|||
|
|
|
|||
|
|
// 短评HTML
|
|||
|
|
$reviewHtml = '';
|
|||
|
|
if (!empty($review)) {
|
|||
|
|
$reviewHtml = '<div class="movie-review-container" style="margin-top:15px;">
|
|||
|
|
<div class="movie-review-title" style="margin:0 0 8px 0; font-size:14px; color:var(--movie-text-primary); font-weight:600; padding-bottom:8px; border-bottom:1px solid var(--movie-border-color);">💭 我的影评</div>
|
|||
|
|
<div class="movie-review-content" style="font-size:14px; line-height:1.8; color:var(--movie-text-primary); text-align:justify; background:var(--movie-review-bg); padding:15px; border-radius:8px; border-left:3px solid var(--movie-primary-color); font-family:\"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;">
|
|||
|
|
' . nl2br($review) . '
|
|||
|
|
</div>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 原始标题显示 - 仅作为鼠标悬停提示
|
|||
|
|
$titleTooltip = '';
|
|||
|
|
if ($originalTitle && $originalTitle != $title) {
|
|||
|
|
$titleTooltip = ' title="原名:' . $originalTitle . '"';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html = <<<HTML
|
|||
|
|
<style>
|
|||
|
|
:root {
|
|||
|
|
--movie-bg-primary: #ffffff;
|
|||
|
|
--movie-bg-secondary: #f9fafb;
|
|||
|
|
--movie-bg-tertiary: #fafafa;
|
|||
|
|
--movie-bg-card: #ffffff;
|
|||
|
|
--movie-text-primary: #1a1a1a;
|
|||
|
|
--movie-text-secondary: #666666;
|
|||
|
|
--movie-text-tertiary: #999999;
|
|||
|
|
--movie-primary-color: #0073aa;
|
|||
|
|
--movie-primary-hover: #0056b3;
|
|||
|
|
--movie-accent-color: #01d277;
|
|||
|
|
--movie-accent-hover: #01b36b;
|
|||
|
|
--movie-border-color: #e8e8e8;
|
|||
|
|
--movie-border-light: #f0f0f0;
|
|||
|
|
--movie-shadow-color: rgba(0,0,0,0.08);
|
|||
|
|
--movie-rating-color: #ffac2d;
|
|||
|
|
--movie-my-rating-color: #ff6b35;
|
|||
|
|
--movie-star-color: #ff6b35;
|
|||
|
|
--movie-star-empty-color: #dddddd;
|
|||
|
|
--movie-button-bg: #f8f9fa;
|
|||
|
|
--movie-button-hover-bg: #e9ecef;
|
|||
|
|
--movie-review-bg: #f9f9f9;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (prefers-color-scheme: dark) {
|
|||
|
|
:root {
|
|||
|
|
--movie-bg-primary: #1a1a1a;
|
|||
|
|
--movie-bg-secondary: #2d2d2d;
|
|||
|
|
--movie-bg-tertiary: #2a2a2a;
|
|||
|
|
--movie-bg-card: #2d2d2d;
|
|||
|
|
--movie-text-primary: #e0e0e0;
|
|||
|
|
--movie-text-secondary: #aaaaaa;
|
|||
|
|
--movie-text-tertiary: #888888;
|
|||
|
|
--movie-primary-color: #4dabf7;
|
|||
|
|
--movie-primary-hover: #74c0fc;
|
|||
|
|
--movie-accent-color: #20c997;
|
|||
|
|
--movie-accent-hover: #12b886;
|
|||
|
|
--movie-border-color: #404040;
|
|||
|
|
--movie-border-light: #333333;
|
|||
|
|
--movie-shadow-color: rgba(0,0,0,0.2);
|
|||
|
|
--movie-rating-color: #ffc107;
|
|||
|
|
--movie-my-rating-color: #ff922b;
|
|||
|
|
--movie-star-color: #ff922b;
|
|||
|
|
--movie-star-empty-color: #555555;
|
|||
|
|
--movie-button-bg: #3a3a3a;
|
|||
|
|
--movie-button-hover-bg: #4a4a4a;
|
|||
|
|
--movie-review-bg: #3a3a3a;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.dark .markdown-body h2{border-bottom:0px!important;margin-top:0px!important;padding-bottom:0px!important;margin-bottom:0px!important;}
|
|||
|
|
.movie-card-content {
|
|||
|
|
background: var(--movie-bg-card);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
box-shadow: 0 4px 12px var(--movie-shadow-color);
|
|||
|
|
overflow: hidden;
|
|||
|
|
border: 1px solid var(--movie-border-color);
|
|||
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-card-content:hover {
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
box-shadow: 0 6px 16px var(--movie-shadow-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-header {
|
|||
|
|
padding: 20px 25px;
|
|||
|
|
background: linear-gradient(135deg, var(--movie-bg-secondary) 0%, var(--movie-bg-tertiary) 100%);
|
|||
|
|
border-bottom: 1px solid var(--movie-border-light);
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-title {
|
|||
|
|
margin: 0;
|
|||
|
|
font-size: 22px;
|
|||
|
|
line-height: 1.3;
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
font-weight: 700;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-title-link {
|
|||
|
|
color: var(--movie-primary-color);
|
|||
|
|
text-decoration: none;
|
|||
|
|
transition: color 0.2s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-title-link:hover {
|
|||
|
|
color: var(--movie-primary-hover);
|
|||
|
|
text-decoration: underline;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-content {
|
|||
|
|
padding: 25px;
|
|||
|
|
border-bottom: 1px solid var(--movie-border-light);
|
|||
|
|
background: var(--movie-bg-card);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 120px 1fr 1fr;
|
|||
|
|
gap: 25px;
|
|||
|
|
align-items: stretch;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.movie-grid {
|
|||
|
|
grid-template-columns: 1fr;
|
|||
|
|
gap: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-grid > div:nth-child(2) {
|
|||
|
|
border-right: none;
|
|||
|
|
padding-right: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-grid > div:nth-child(3) {
|
|||
|
|
padding-left: 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster-container {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster-link {
|
|||
|
|
display: block;
|
|||
|
|
text-decoration: none;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster-wrapper {
|
|||
|
|
position: relative;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster-frame {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 0;
|
|||
|
|
padding-bottom: 150%;
|
|||
|
|
position: relative;
|
|||
|
|
overflow: hidden;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
border: 3px solid var(--movie-bg-card);
|
|||
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
|
|||
|
|
background: var(--movie-bg-tertiary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 0;
|
|||
|
|
left: 0;
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
object-fit: cover;
|
|||
|
|
transition: transform 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster:hover {
|
|||
|
|
transform: scale(1.05);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster-badge {
|
|||
|
|
position: absolute;
|
|||
|
|
top: -5px;
|
|||
|
|
right: -5px;
|
|||
|
|
background: var(--movie-accent-color);
|
|||
|
|
color: white;
|
|||
|
|
width: 24px;
|
|||
|
|
height: 24px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
box-shadow: 0 2px 4px rgba(1,210,119,0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-info-left {
|
|||
|
|
padding-right: 15px;
|
|||
|
|
border-right: 1px solid var(--movie-border-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-info-item {
|
|||
|
|
margin: 0 0 10px 0;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: var(--movie-text-secondary);
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-info-label {
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
display: inline-block;
|
|||
|
|
width: 70px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-info-value {
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-genres {
|
|||
|
|
white-space: nowrap;
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
display: inline;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-info-right {
|
|||
|
|
padding-left: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-footer {
|
|||
|
|
padding: 20px 25px 25px 25px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-summary-section {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-summary-header {
|
|||
|
|
margin: 0 0 12px 0;
|
|||
|
|
font-size: 16px;
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
font-weight: 600;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
padding-bottom: 8px;
|
|||
|
|
border-bottom: 1px solid var(--movie-border-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-summary-icon {
|
|||
|
|
background: var(--movie-accent-color);
|
|||
|
|
color: white;
|
|||
|
|
width: 28px;
|
|||
|
|
height: 28px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-summary-content {
|
|||
|
|
background: var(--movie-bg-tertiary);
|
|||
|
|
padding: 20px;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
border: 1px solid var(--movie-border-color);
|
|||
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<div class="movie-card">
|
|||
|
|
<div class="movie-card-content">
|
|||
|
|
<!-- 上栏:标题 -->
|
|||
|
|
<div class="movie-header">
|
|||
|
|
<h2 class="movie-title">
|
|||
|
|
<a href="https://www.themoviedb.org/movie/{$movieId}" target="_blank"
|
|||
|
|
class="movie-title-link" {$titleTooltip}>
|
|||
|
|
《{$title}》
|
|||
|
|
</a>
|
|||
|
|
</h2>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 中栏:三列布局 -->
|
|||
|
|
<div class="movie-content">
|
|||
|
|
<div class="movie-grid">
|
|||
|
|
<!-- 左侧:电影海报 -->
|
|||
|
|
<div class="movie-poster-container">
|
|||
|
|
<a href="https://www.themoviedb.org/movie/{$movieId}" target="_blank" class="movie-poster-link">
|
|||
|
|
<div class="movie-poster-wrapper">
|
|||
|
|
<div class="movie-poster-frame">
|
|||
|
|
<img src="{$posterSrc}" alt="{$title}" class="movie-poster"
|
|||
|
|
onerror="this.src='{$defaultPoster}';">
|
|||
|
|
</div>
|
|||
|
|
<div class="movie-poster-badge">🎬</div>
|
|||
|
|
</div>
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 中间:电影信息 - 修复:按新顺序显示 -->
|
|||
|
|
<div class="movie-info-left">
|
|||
|
|
<!-- 导演 -->
|
|||
|
|
<div class="movie-info-item">
|
|||
|
|
<span class="movie-info-label">导演:</span>
|
|||
|
|
<span class="movie-info-value" style="color:var(--movie-primary-color);">{$director}</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 类型 - 修复:确保在一行显示 -->
|
|||
|
|
<div class="movie-info-item">
|
|||
|
|
<span class="movie-info-label">类型:</span>
|
|||
|
|
<span class="movie-info-value movie-genres" title="{$genresHtml}">{$genresHtml}</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 年份 -->
|
|||
|
|
<div class="movie-info-item">
|
|||
|
|
<span class="movie-info-label">年份:</span>
|
|||
|
|
<span class="movie-info-value">{$releaseYear}</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 时长 -->
|
|||
|
|
<div class="movie-info-item">
|
|||
|
|
<span class="movie-info-label">时长:</span>
|
|||
|
|
<span class="movie-info-value">{$runtime}</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 地区 -->
|
|||
|
|
<div class="movie-info-item">
|
|||
|
|
<span class="movie-info-label">地区:</span>
|
|||
|
|
<span class="movie-info-value">{$countriesHtml}</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 评分 -->
|
|||
|
|
{$ratingHtml}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 右侧:自定义信息 -->
|
|||
|
|
<div class="movie-info-right">
|
|||
|
|
{$rightColumnHtml}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 下栏:剧情简介和影评 -->
|
|||
|
|
<div class="movie-footer">
|
|||
|
|
<!-- 剧情简介 -->
|
|||
|
|
<div class="movie-summary-section">
|
|||
|
|
<div class="movie-summary-header">
|
|||
|
|
<span class="movie-summary-icon">📽️</span>
|
|||
|
|
<span>剧情简介</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="movie-summary-content">
|
|||
|
|
{$summaryHtml}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 影评 -->
|
|||
|
|
{$reviewHtml}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
HTML;
|
|||
|
|
|
|||
|
|
// 添加JavaScript切换函数
|
|||
|
|
$html .= '
|
|||
|
|
<script>
|
|||
|
|
function toggleMovieSummary(element) {
|
|||
|
|
var container = element.closest(".movie-summary-container");
|
|||
|
|
var shortDiv = container.querySelector(".movie-summary-short");
|
|||
|
|
var fullDiv = container.querySelector(".movie-summary-full");
|
|||
|
|
|
|||
|
|
if (shortDiv && fullDiv) {
|
|||
|
|
if (shortDiv.style.display === "none") {
|
|||
|
|
// 收起
|
|||
|
|
shortDiv.style.display = "block";
|
|||
|
|
fullDiv.style.display = "none";
|
|||
|
|
} else {
|
|||
|
|
// 展开
|
|||
|
|
shortDiv.style.display = "none";
|
|||
|
|
fullDiv.style.display = "block";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>';
|
|||
|
|
|
|||
|
|
return $html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取所有电影数据(支持分页)
|
|||
|
|
*/
|
|||
|
|
private static function getAllMoviesData($page = 1, $pageSize = 10)
|
|||
|
|
{
|
|||
|
|
$cacheDir = dirname(__FILE__) . '/cache/';
|
|||
|
|
$allMovies = array();
|
|||
|
|
|
|||
|
|
// 扫描缓存目录
|
|||
|
|
if (file_exists($cacheDir)) {
|
|||
|
|
$files = scandir($cacheDir);
|
|||
|
|
foreach ($files as $file) {
|
|||
|
|
if (pathinfo($file, PATHINFO_EXTENSION) === 'json') {
|
|||
|
|
$filePath = $cacheDir . $file;
|
|||
|
|
$content = file_get_contents($filePath);
|
|||
|
|
if ($content) {
|
|||
|
|
$data = json_decode($content, true);
|
|||
|
|
if ($data && isset($data['fetched_at'])) {
|
|||
|
|
$movieId = pathinfo($file, PATHINFO_FILENAME);
|
|||
|
|
$data['movieId'] = $movieId;
|
|||
|
|
|
|||
|
|
if (file_exists($filePath)) {
|
|||
|
|
$data['added_time'] = filemtime($filePath);
|
|||
|
|
} else {
|
|||
|
|
$data['added_time'] = isset($data['fetched_at']) ? $data['fetched_at'] : time();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$allMovies[] = $data;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按added_time倒序排序
|
|||
|
|
usort($allMovies, function($a, $b) {
|
|||
|
|
return $b['added_time'] - $a['added_time'];
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
$total = count($allMovies);
|
|||
|
|
$totalPages = ceil($total / $pageSize);
|
|||
|
|
|
|||
|
|
// 限制页码范围
|
|||
|
|
$page = max(1, min($page, $totalPages));
|
|||
|
|
|
|||
|
|
// 分页处理
|
|||
|
|
$startIndex = ($page - 1) * $pageSize;
|
|||
|
|
$paginatedMovies = array_slice($allMovies, $startIndex, $pageSize);
|
|||
|
|
|
|||
|
|
return array(
|
|||
|
|
'movies' => $paginatedMovies,
|
|||
|
|
'total' => $total,
|
|||
|
|
'page' => $page,
|
|||
|
|
'pageSize' => $pageSize,
|
|||
|
|
'totalPages' => $totalPages,
|
|||
|
|
'startIndex' => $startIndex + 1,
|
|||
|
|
'endIndex' => min($startIndex + $pageSize, $total)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 渲染所有电影列表(简单文本格式)- 支持分页
|
|||
|
|
*/
|
|||
|
|
public static function renderAllMovies($page = 1)
|
|||
|
|
{
|
|||
|
|
// 从GET参数获取页码
|
|||
|
|
if (isset($_GET['page']) && is_numeric($_GET['page'])) {
|
|||
|
|
$page = intval($_GET['page']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options')->plugin('MovieInfo');
|
|||
|
|
$pageSize = isset($options->pageSize) ? intval($options->pageSize) : 10;
|
|||
|
|
$imageProxy = isset($options->imageProxy) ? $options->imageProxy : 'https://images.weserv.nl/?url=';
|
|||
|
|
$defaultPoster = isset($options->defaultPoster) ? $options->defaultPoster :
|
|||
|
|
'https://via.placeholder.com/300x450/CCCCCC/666666?text=No+Poster';
|
|||
|
|
|
|||
|
|
// 获取分页数据
|
|||
|
|
$paginationData = self::getAllMoviesData($page, $pageSize);
|
|||
|
|
$allMovies = $paginationData['movies'];
|
|||
|
|
$total = $paginationData['total'];
|
|||
|
|
$currentPage = $paginationData['page'];
|
|||
|
|
$totalPages = $paginationData['totalPages'];
|
|||
|
|
$startIndex = $paginationData['startIndex'];
|
|||
|
|
$endIndex = $paginationData['endIndex'];
|
|||
|
|
|
|||
|
|
if (empty($allMovies)) {
|
|||
|
|
return '<div style="padding:20px; text-align:center; color:var(--movie-text-secondary); background:var(--movie-bg-tertiary); border-radius:8px; border:1px solid var(--movie-border-color);">
|
|||
|
|
<p>暂无电影数据</p>
|
|||
|
|
<p style="font-size:13px; color:var(--movie-text-tertiary);">请先在文章中使用[movie:ID]短代码添加电影</p>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html = '<style>
|
|||
|
|
.movies-list-container {
|
|||
|
|
margin: 20px 0;
|
|||
|
|
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-header {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
padding: 20px;
|
|||
|
|
background: var(--movie-bg-card);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
border: 1px solid var(--movie-border-color);
|
|||
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|||
|
|
text-align: center;
|
|||
|
|
border-left: 4px solid var(--movie-accent-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-title {
|
|||
|
|
margin: 0;
|
|||
|
|
font-size: 24px;
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-subtitle {
|
|||
|
|
margin: 8px 0 0 0;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: var(--movie-text-secondary);
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-pagination-info {
|
|||
|
|
margin-bottom: 25px;
|
|||
|
|
padding: 18px;
|
|||
|
|
background: var(--movie-bg-card);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
border: 1px solid var(--movie-border-color);
|
|||
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.03);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-info-row {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-info-item {
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: var(--movie-text-secondary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-info-label {
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-count {
|
|||
|
|
color: var(--movie-accent-color);
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-total {
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-current-page {
|
|||
|
|
color: var(--movie-my-rating-color);
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movies-total-pages {
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-item-card {
|
|||
|
|
margin-bottom: 25px;
|
|||
|
|
padding: 0;
|
|||
|
|
background: var(--movie-bg-card);
|
|||
|
|
border-radius: 10px;
|
|||
|
|
border: 1px solid var(--movie-border-color);
|
|||
|
|
box-shadow: 0 3px 10px rgba(0,0,0,0.06);
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-item-card:hover {
|
|||
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
|||
|
|
transform: translateY(-3px);
|
|||
|
|
border-color: var(--movie-accent-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-item-content {
|
|||
|
|
display: flex;
|
|||
|
|
padding: 20px;
|
|||
|
|
gap: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.movie-item-content {
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster-container {
|
|||
|
|
width: 120px;
|
|||
|
|
margin: 0 auto 10px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster-container {
|
|||
|
|
position: relative;
|
|||
|
|
width: 100px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster-wrapper {
|
|||
|
|
position: relative;
|
|||
|
|
width: 100%;
|
|||
|
|
height: 0;
|
|||
|
|
padding-bottom: 150%;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
background: var(--movie-bg-tertiary);
|
|||
|
|
border: 1px solid var(--movie-border-color);
|
|||
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 0;
|
|||
|
|
left: 0;
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
object-fit: cover;
|
|||
|
|
transition: transform 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-poster:hover {
|
|||
|
|
transform: scale(1.05);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-index-on-poster {
|
|||
|
|
position: absolute;
|
|||
|
|
top: -8px;
|
|||
|
|
right: -8px;
|
|||
|
|
width: 32px;
|
|||
|
|
height: 32px;
|
|||
|
|
background: linear-gradient(135deg, var(--index-color));
|
|||
|
|
color: white;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-weight: bold;
|
|||
|
|
font-size: 16px;
|
|||
|
|
box-shadow: 0 3px 8px rgba(0,0,0,0.2);
|
|||
|
|
border: 2px solid var(--movie-bg-card);
|
|||
|
|
z-index: 10;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-details {
|
|||
|
|
flex-grow: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-title-line {
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
font-size: 16px;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-title-link {
|
|||
|
|
color: var(--movie-accent-color);
|
|||
|
|
text-decoration: none;
|
|||
|
|
font-weight: bold;
|
|||
|
|
font-size: 18px;
|
|||
|
|
transition: color 0.2s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-title-link:hover {
|
|||
|
|
color: var(--movie-accent-hover);
|
|||
|
|
text-decoration: underline;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-separator {
|
|||
|
|
color: var(--movie-text-tertiary);
|
|||
|
|
font-size: 14px;
|
|||
|
|
margin: 0 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-category {
|
|||
|
|
color: var(--movie-accent-color);
|
|||
|
|
font-size: 14px;
|
|||
|
|
margin-left: 5px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
background: rgba(1, 210, 119, 0.1);
|
|||
|
|
padding: 2px 8px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
border: 1px solid rgba(1, 210, 119, 0.2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-date {
|
|||
|
|
color: var(--movie-my-rating-color);
|
|||
|
|
font-size: 14px;
|
|||
|
|
margin-left: 5px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-method {
|
|||
|
|
color: #27ae60;
|
|||
|
|
font-size: 14px;
|
|||
|
|
margin-left: 5px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
background: rgba(39, 174, 96, 0.1);
|
|||
|
|
padding: 2px 8px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
border: 1px solid rgba(39, 174, 96, 0.2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-info-line {
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: var(--movie-text-secondary);
|
|||
|
|
line-height: 1.5;
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-director {
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-director-label {
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-genres {
|
|||
|
|
color: var(--movie-text-tertiary);
|
|||
|
|
white-space: normal;
|
|||
|
|
display: inline;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-genre-separator {
|
|||
|
|
margin: 0 4px;
|
|||
|
|
color: var(--movie-text-tertiary);
|
|||
|
|
font-weight: 300;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-year {
|
|||
|
|
color: var(--movie-text-tertiary);
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-runtime {
|
|||
|
|
color: var(--movie-text-tertiary);
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-rating-value {
|
|||
|
|
color: var(--movie-rating-color);
|
|||
|
|
font-weight: bold;
|
|||
|
|
background: rgba(255, 172, 45, 0.1);
|
|||
|
|
padding: 2px 6px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
border: 1px solid rgba(255, 172, 45, 0.2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-no-rating {
|
|||
|
|
color: var(--movie-text-tertiary);
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-my-rating-value {
|
|||
|
|
color: var(--movie-my-rating-color);
|
|||
|
|
font-weight: bold;
|
|||
|
|
background: rgba(255, 107, 53, 0.1);
|
|||
|
|
padding: 2px 6px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
border: 1px solid rgba(255, 107, 53, 0.2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-review-section {
|
|||
|
|
margin-top: 12px;
|
|||
|
|
padding-top: 12px;
|
|||
|
|
border-top: 1px dashed var(--movie-border-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-review-title {
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
font-weight: 600;
|
|||
|
|
margin-bottom: 5px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-review-content {
|
|||
|
|
font-size: 14px;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
color: var(--movie-text-primary);
|
|||
|
|
background: var(--movie-review-bg);
|
|||
|
|
padding: 12px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
border-left: 3px solid var(--movie-accent-color);
|
|||
|
|
}
|
|||
|
|
</style>';
|
|||
|
|
|
|||
|
|
$html .= '<div class="movies-list-container">';
|
|||
|
|
|
|||
|
|
// 标题模块 - 调整深色模式
|
|||
|
|
$html .= '<div class="movies-header">
|
|||
|
|
<h2 class="movies-title">🎬 我的全部已看电影</h2>
|
|||
|
|
<p class="movies-subtitle">已记录 <span style="color:var(--movie-accent-color);font-weight:600;">' . $total . '</span> 部电影,继续探索更多精彩影片</p>
|
|||
|
|
</div>';
|
|||
|
|
|
|||
|
|
// 页码信息 - 调整深色模式
|
|||
|
|
$html .= '<div class="movies-pagination-info">
|
|||
|
|
<div class="movies-info-row">
|
|||
|
|
<div class="movies-info-item">
|
|||
|
|
<span class="movies-info-label">显示范围:</span>
|
|||
|
|
<span class="movies-count">' . $startIndex . '-' . $endIndex . '</span>
|
|||
|
|
<span class="movies-info-label"> / 总计:</span>
|
|||
|
|
<span class="movies-total">' . $total . '</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="movies-info-item">
|
|||
|
|
<span class="movies-info-label">当前页码:</span>
|
|||
|
|
<span class="movies-current-page">第 ' . $currentPage . ' 页</span>
|
|||
|
|
<span class="movies-info-label"> / 总页数:</span>
|
|||
|
|
<span class="movies-total-pages">' . $totalPages . ' 页</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>';
|
|||
|
|
|
|||
|
|
// 计算倒序序号
|
|||
|
|
$totalCount = $total;
|
|||
|
|
$currentIndex = $totalCount - (($currentPage - 1) * $pageSize);
|
|||
|
|
|
|||
|
|
foreach ($allMovies as $movie) {
|
|||
|
|
$movieId = $movie['movieId'];
|
|||
|
|
$title = isset($movie['title']) ? htmlspecialchars($movie['title']) : '未知电影';
|
|||
|
|
$originalTitle = isset($movie['original_title']) ? htmlspecialchars($movie['original_title']) : '';
|
|||
|
|
|
|||
|
|
// 海报URL
|
|||
|
|
$posterPath = isset($movie['poster_path']) ? $movie['poster_path'] : '';
|
|||
|
|
if ($posterPath) {
|
|||
|
|
$posterUrl = 'https://image.tmdb.org/t/p/w200' . $posterPath;
|
|||
|
|
$posterSrc = $imageProxy . urlencode($posterUrl);
|
|||
|
|
} else {
|
|||
|
|
$posterSrc = $defaultPoster;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 自定义字段
|
|||
|
|
$startDate = isset($movie['custom_start_date']) ? htmlspecialchars($movie['custom_start_date']) : '';
|
|||
|
|
$watchDate = isset($movie['custom_watch_date']) ? htmlspecialchars($movie['custom_watch_date']) : '';
|
|||
|
|
$watchMethod = isset($movie['custom_watch_method']) ? htmlspecialchars($movie['custom_watch_method']) : '';
|
|||
|
|
$movieCategory = isset($movie['custom_movie_category']) ? htmlspecialchars($movie['custom_movie_category']) : '';
|
|||
|
|
$myRating = isset($movie['custom_my_rating']) ? htmlspecialchars($movie['custom_my_rating']) : '';
|
|||
|
|
|
|||
|
|
// 电影信息
|
|||
|
|
$releaseYear = isset($movie['release_year']) ? htmlspecialchars($movie['release_year']) : '未知';
|
|||
|
|
$director = isset($movie['director']) ? htmlspecialchars($movie['director']) : '未知';
|
|||
|
|
|
|||
|
|
// 类型 - 使用·分隔符
|
|||
|
|
$genres = '未知';
|
|||
|
|
if (isset($movie['genres']) && is_array($movie['genres'])) {
|
|||
|
|
$genres = implode('<span class="movie-genre-separator">·</span>', $movie['genres']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 时长
|
|||
|
|
$runtime = isset($movie['runtime']) ? htmlspecialchars($movie['runtime']) . '分钟' : '未知';
|
|||
|
|
|
|||
|
|
// 评分
|
|||
|
|
$rating = isset($movie['vote_average']) ? floatval($movie['vote_average']) : 0;
|
|||
|
|
$ratingDisplay = $rating > 0 ? number_format($rating, 1) . '分' : '暂无评分';
|
|||
|
|
|
|||
|
|
// 短评
|
|||
|
|
$review = isset($movie['review']) ? htmlspecialchars($movie['review']) : '';
|
|||
|
|
|
|||
|
|
// 原始标题作为工具提示
|
|||
|
|
$titleTooltip = '';
|
|||
|
|
if ($originalTitle && $originalTitle != $title) {
|
|||
|
|
$titleTooltip = ' title="原名:' . $originalTitle . '"';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取序号颜色
|
|||
|
|
$indexColors = [
|
|||
|
|
'#01d277, #01b36b', // TMDB绿色
|
|||
|
|
'#0073aa, #0056b3', // 蓝色
|
|||
|
|
'#e67e22, #d35400', // 橙色
|
|||
|
|
'#9b59b6, #8e44ad', // 紫色
|
|||
|
|
'#e74c3c, #c0392b', // 红色
|
|||
|
|
];
|
|||
|
|
$indexColor = $indexColors[($currentIndex - 1) % count($indexColors)];
|
|||
|
|
|
|||
|
|
$html .= '<div class="movie-item-card">
|
|||
|
|
<div class="movie-item-content">
|
|||
|
|
<!-- 左侧:电影海报 -->
|
|||
|
|
<div class="movie-poster-container">
|
|||
|
|
<a href="https://www.themoviedb.org/movie/' . $movieId . '" target="_blank" style="text-decoration:none;">
|
|||
|
|
<div class="movie-poster-wrapper">
|
|||
|
|
<img src="' . $posterSrc . '" alt="' . $title . '" class="movie-poster"
|
|||
|
|
onerror="this.src=\'' . $defaultPoster . '\';">
|
|||
|
|
<!-- 序号显示在封面上 -->
|
|||
|
|
<div class="movie-index-on-poster" style="--index-color:' . $indexColor . ';">' . $currentIndex . '</div>
|
|||
|
|
</div>
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 右侧:电影详情 -->
|
|||
|
|
<div class="movie-details">
|
|||
|
|
<!-- 第一行:电影名/分类/观看日期/观看方式 -->
|
|||
|
|
<div class="movie-title-line">
|
|||
|
|
<a href="https://www.themoviedb.org/movie/' . $movieId . '" target="_blank"
|
|||
|
|
class="movie-title-link"' . $titleTooltip . '>《' . $title . '》</a>';
|
|||
|
|
|
|||
|
|
if ($movieCategory) {
|
|||
|
|
$html .= '<span class="movie-separator">/</span><span class="movie-category">' . $movieCategory . '</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理观看日期显示
|
|||
|
|
if ($startDate && $watchDate) {
|
|||
|
|
if ($startDate === $watchDate) {
|
|||
|
|
$html .= '<span class="movie-separator">/</span><span class="movie-date">' . $startDate . '</span>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<span class="movie-separator">/</span><span class="movie-date">' . $startDate . '-' . $watchDate . '</span>';
|
|||
|
|
}
|
|||
|
|
} elseif ($startDate) {
|
|||
|
|
$html .= '<span class="movie-separator">/</span><span class="movie-date">' . $startDate . '-至今</span>';
|
|||
|
|
} elseif ($watchDate) {
|
|||
|
|
$html .= '<span class="movie-separator">/</span><span class="movie-date">未知-' . $watchDate . '</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($watchMethod) {
|
|||
|
|
$html .= '<span class="movie-separator">/</span><span class="movie-method">' . $watchMethod . '</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</div>';
|
|||
|
|
|
|||
|
|
// 第二行:导演/类型/年份/时长/评分 - 修改:导演前面加"导演:"
|
|||
|
|
$html .= '<div class="movie-info-line">';
|
|||
|
|
|
|||
|
|
$html .= '<span class="movie-director-label">导演:</span><span class="movie-director">' . $director . '</span>';
|
|||
|
|
$html .= '<span class="movie-separator">/</span>';
|
|||
|
|
|
|||
|
|
// 类型显示 - 使用·分隔符
|
|||
|
|
$html .= '<span class="movie-genres">' . $genres . '</span>';
|
|||
|
|
$html .= '<span class="movie-separator">/</span>';
|
|||
|
|
|
|||
|
|
$html .= '<span class="movie-year">' . $releaseYear . '年</span>';
|
|||
|
|
$html .= '<span class="movie-separator">/</span>';
|
|||
|
|
$html .= '<span class="movie-runtime">' . $runtime . '</span>';
|
|||
|
|
$html .= '<span class="movie-separator">/</span>';
|
|||
|
|
|
|||
|
|
if ($rating > 0) {
|
|||
|
|
$html .= '<span class="movie-rating-value">评分:' . $ratingDisplay . '</span>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<span class="movie-no-rating">' . $ratingDisplay . '</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($myRating) {
|
|||
|
|
$html .= '<span class="movie-separator">/</span><span class="movie-my-rating-value">我的评分:' . $myRating . '</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</div>';
|
|||
|
|
|
|||
|
|
// 第三行:短评
|
|||
|
|
if ($review) {
|
|||
|
|
$html .= '<div class="movie-review-section">
|
|||
|
|
<div class="movie-review-title">📝 影评</div>
|
|||
|
|
<div class="movie-review-content">' . nl2br($review) . '</div>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</div>
|
|||
|
|
</div>
|
|||
|
|
</div>';
|
|||
|
|
|
|||
|
|
$currentIndex--;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 分页导航
|
|||
|
|
if ($totalPages > 1) {
|
|||
|
|
$html .= self::generatePagination($currentPage, $totalPages);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</div>';
|
|||
|
|
|
|||
|
|
return $html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成分页HTML
|
|||
|
|
*/
|
|||
|
|
private static function generatePagination($currentPage, $totalPages, $baseUrl = '')
|
|||
|
|
{
|
|||
|
|
if ($totalPages <= 1) {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html = '<div class="movie-pagination" style="margin:30px 0; text-align:center;">
|
|||
|
|
<div style="display:inline-flex; align-items:center; gap:8px; background:var(--movie-bg-card); padding:12px 20px; border-radius:8px; border:1px solid var(--movie-border-color); box-shadow:0 2px 8px rgba(0,0,0,0.05);">';
|
|||
|
|
|
|||
|
|
// 首页
|
|||
|
|
if ($currentPage > 1) {
|
|||
|
|
$html .= '<a href="' . self::getPageUrl(1, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
|
|||
|
|
onmouseover="this.style.background=\'var(--movie-primary-color)\'; this.style.color=\'white\';"
|
|||
|
|
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';">«</a>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-text-tertiary); font-size:14px; font-weight:600;">«</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 上一页
|
|||
|
|
if ($currentPage > 1) {
|
|||
|
|
$html .= '<a href="' . self::getPageUrl($currentPage - 1, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
|
|||
|
|
onmouseover="this.style.background=\'var(--movie-primary-color)\'; this.style.color=\'white\';"
|
|||
|
|
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';">‹</a>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-text-tertiary); font-size:14px; font-weight:600;">‹</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 页码显示
|
|||
|
|
$startPage = max(1, $currentPage - 2);
|
|||
|
|
$endPage = min($totalPages, $currentPage + 2);
|
|||
|
|
|
|||
|
|
for ($i = $startPage; $i <= $endPage; $i++) {
|
|||
|
|
if ($i == $currentPage) {
|
|||
|
|
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-primary-color); color:white; font-size:14px; font-weight:600;">' . $i . '</span>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<a href="' . self::getPageUrl($i, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
|
|||
|
|
onmouseover="this.style.background=\'var(--movie-accent-color)\'; this.style.color=\'white\';"
|
|||
|
|
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';">' . $i . '</a>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 下一页
|
|||
|
|
if ($currentPage < $totalPages) {
|
|||
|
|
$html .= '<a href="' . self::getPageUrl($currentPage + 1, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
|
|||
|
|
onmouseover="this.style.background=\'var(--movie-primary-color)\'; this.style.color=\'white\';"
|
|||
|
|
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';">›</a>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-text-tertiary); font-size:14px; font-weight:600;">›</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 末页
|
|||
|
|
if ($currentPage < $totalPages) {
|
|||
|
|
$html .= '<a href="' . self::getPageUrl($totalPages, $baseUrl) . '" style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-primary-color); text-decoration:none; font-size:14px; font-weight:600; transition:all 0.2s ease;"
|
|||
|
|
onmouseover="this.style.background=\'var(--movie-primary-color)\'; this.style.color=\'white\';"
|
|||
|
|
onmouseout="this.style.background=\'var(--movie-button-bg)\'; this.style.color=\'var(--movie-primary-color)\';">»</a>';
|
|||
|
|
} else {
|
|||
|
|
$html .= '<span style="display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:6px; background:var(--movie-button-bg); color:var(--movie-text-tertiary); font-size:14px; font-weight:600;">»</span>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</div></div>';
|
|||
|
|
|
|||
|
|
return $html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取页面URL
|
|||
|
|
*/
|
|||
|
|
private static function getPageUrl($page, $baseUrl = '')
|
|||
|
|
{
|
|||
|
|
if (empty($baseUrl)) {
|
|||
|
|
$currentUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
|
|||
|
|
$currentUrl = preg_replace('/[&?]page=\d+/', '', $currentUrl);
|
|||
|
|
$currentUrl = rtrim($currentUrl, '?&');
|
|||
|
|
|
|||
|
|
$separator = strpos($currentUrl, '?') === false ? '?' : '&';
|
|||
|
|
return $currentUrl . $separator . 'page=' . $page;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $baseUrl . (strpos($baseUrl, '?') === false ? '?' : '&') . 'page=' . $page;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取序号颜色
|
|||
|
|
*/
|
|||
|
|
private static function getIndexColor($index)
|
|||
|
|
{
|
|||
|
|
$colors = [
|
|||
|
|
'#01d277, #01b36b', // TMDB绿色
|
|||
|
|
'#0073aa, #0056b3', // 蓝色
|
|||
|
|
'#e67e22, #d35400', // 橙色
|
|||
|
|
'#9b59b6, #8e44ad', // 紫色
|
|||
|
|
'#e74c3c, #c0392b', // 红色
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return $colors[($index - 1) % count($colors)];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取电影数据(包含影评和自定义信息)
|
|||
|
|
*/
|
|||
|
|
private static function getMovieData($movieId, $review = '', $customData = array())
|
|||
|
|
{
|
|||
|
|
if (!is_numeric($movieId)) return null;
|
|||
|
|
|
|||
|
|
$cacheFile = dirname(__FILE__) . '/cache/' . $movieId . '.json';
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options')->plugin('MovieInfo');
|
|||
|
|
$cacheEnable = isset($options->cacheEnable) ? $options->cacheEnable : '1';
|
|||
|
|
$cacheTime = isset($options->cacheTime) ? intval($options->cacheTime) : 7;
|
|||
|
|
$apiKey = isset($options->tmdbApiKey) ? $options->tmdbApiKey : '';
|
|||
|
|
$language = isset($options->language) ? $options->language : 'zh-CN';
|
|||
|
|
|
|||
|
|
if (empty($apiKey)) {
|
|||
|
|
return array(
|
|||
|
|
'title' => '请配置TMDB API Key',
|
|||
|
|
'original_title' => 'Please configure TMDB API Key',
|
|||
|
|
'error' => 'API_KEY_MISSING'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$data = null;
|
|||
|
|
$needUpdate = false;
|
|||
|
|
|
|||
|
|
// 检查缓存
|
|||
|
|
if ($cacheEnable == '1' && file_exists($cacheFile)) {
|
|||
|
|
$fileTime = filemtime($cacheFile);
|
|||
|
|
$expireTime = $cacheTime * 24 * 3600;
|
|||
|
|
if (time() - $fileTime < $expireTime) {
|
|||
|
|
$cacheContent = file_get_contents($cacheFile);
|
|||
|
|
if ($cacheContent) {
|
|||
|
|
$data = json_decode($cacheContent, true);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果缓存不存在或已过期,从TMDB API获取
|
|||
|
|
if (!$data || empty($data['title'])) {
|
|||
|
|
$data = self::fetchFromTMDB($movieId, $apiKey, $language);
|
|||
|
|
$needUpdate = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新影评
|
|||
|
|
if (!empty($review) && (!isset($data['review']) || $data['review'] !== $review)) {
|
|||
|
|
$data['review'] = $review;
|
|||
|
|
$data['review_updated'] = time();
|
|||
|
|
$needUpdate = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新自定义数据
|
|||
|
|
if (!empty($customData)) {
|
|||
|
|
if (isset($customData['startDate']) && (!isset($data['custom_start_date']) || $data['custom_start_date'] !== $customData['startDate'])) {
|
|||
|
|
$data['custom_start_date'] = $customData['startDate'];
|
|||
|
|
$needUpdate = true;
|
|||
|
|
}
|
|||
|
|
if (isset($customData['watchDate']) && (!isset($data['custom_watch_date']) || $data['custom_watch_date'] !== $customData['watchDate'])) {
|
|||
|
|
$data['custom_watch_date'] = $customData['watchDate'];
|
|||
|
|
$needUpdate = true;
|
|||
|
|
}
|
|||
|
|
if (isset($customData['watchMethod']) && (!isset($data['custom_watch_method']) || $data['custom_watch_method'] !== $customData['watchMethod'])) {
|
|||
|
|
$data['custom_watch_method'] = $customData['watchMethod'];
|
|||
|
|
$needUpdate = true;
|
|||
|
|
}
|
|||
|
|
if (isset($customData['movieCategory']) && (!isset($data['custom_movie_category']) || $data['custom_movie_category'] !== $customData['movieCategory'])) {
|
|||
|
|
$data['custom_movie_category'] = $customData['movieCategory'];
|
|||
|
|
$needUpdate = true;
|
|||
|
|
}
|
|||
|
|
if (isset($customData['recommendation']) && (!isset($data['custom_recommendation']) || $data['custom_recommendation'] != $customData['recommendation'])) {
|
|||
|
|
$data['custom_recommendation'] = intval($customData['recommendation']);
|
|||
|
|
$needUpdate = true;
|
|||
|
|
}
|
|||
|
|
if (isset($customData['myRating']) && (!isset($data['custom_my_rating']) || $data['custom_my_rating'] !== $customData['myRating'])) {
|
|||
|
|
$data['custom_my_rating'] = $customData['myRating'];
|
|||
|
|
$needUpdate = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保自定义字段存在
|
|||
|
|
$customFields = array(
|
|||
|
|
'custom_start_date' => '',
|
|||
|
|
'custom_watch_date' => '',
|
|||
|
|
'custom_watch_method' => '',
|
|||
|
|
'custom_movie_category' => '',
|
|||
|
|
'custom_recommendation' => 0,
|
|||
|
|
'custom_my_rating' => ''
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
foreach ($customFields as $field => $default) {
|
|||
|
|
if (!isset($data[$field])) {
|
|||
|
|
$data[$field] = $default;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果需要更新缓存,保存到文件
|
|||
|
|
if ($needUpdate && $data && !empty($data['title'])) {
|
|||
|
|
file_put_contents($cacheFile, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $data;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从TMDB API获取数据
|
|||
|
|
*/
|
|||
|
|
private static function fetchFromTMDB($movieId, $apiKey, $language)
|
|||
|
|
{
|
|||
|
|
// TMDB API URL
|
|||
|
|
$url = "https://api.themoviedb.org/3/movie/{$movieId}?api_key={$apiKey}&language={$language}&append_to_response=credits";
|
|||
|
|
|
|||
|
|
$ch = curl_init();
|
|||
|
|
curl_setopt_array($ch, array(
|
|||
|
|
CURLOPT_URL => $url,
|
|||
|
|
CURLOPT_RETURNTRANSFER => true,
|
|||
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|||
|
|
CURLOPT_TIMEOUT => 20,
|
|||
|
|
CURLOPT_SSL_VERIFYPEER => false,
|
|||
|
|
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|||
|
|
CURLOPT_ENCODING => 'gzip, deflate',
|
|||
|
|
CURLOPT_HTTPHEADER => array(
|
|||
|
|
'Accept: application/json',
|
|||
|
|
'Cache-Control: no-cache'
|
|||
|
|
)
|
|||
|
|
));
|
|||
|
|
|
|||
|
|
$response = curl_exec($ch);
|
|||
|
|
if (curl_errno($ch)) {
|
|||
|
|
curl_close($ch);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|||
|
|
curl_close($ch);
|
|||
|
|
|
|||
|
|
if ($httpCode != 200 || empty($response)) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$movieData = json_decode($response, true);
|
|||
|
|
if (!$movieData) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$data = array();
|
|||
|
|
|
|||
|
|
// 1. 基本信息
|
|||
|
|
$data['title'] = isset($movieData['title']) ? trim($movieData['title']) : '未知电影';
|
|||
|
|
$data['original_title'] = isset($movieData['original_title']) ? trim($movieData['original_title']) : '';
|
|||
|
|
|
|||
|
|
// 2. 海报
|
|||
|
|
$data['poster_path'] = isset($movieData['poster_path']) ? $movieData['poster_path'] : '';
|
|||
|
|
|
|||
|
|
// 3. 简介
|
|||
|
|
$data['overview'] = isset($movieData['overview']) ? trim($movieData['overview']) : '暂无简介';
|
|||
|
|
|
|||
|
|
// 4. 上映年份
|
|||
|
|
if (isset($movieData['release_date']) && !empty($movieData['release_date'])) {
|
|||
|
|
$releaseDate = $movieData['release_date'];
|
|||
|
|
$data['release_year'] = substr($releaseDate, 0, 4);
|
|||
|
|
} else {
|
|||
|
|
$data['release_year'] = '未知';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. 导演
|
|||
|
|
$director = '未知';
|
|||
|
|
if (isset($movieData['credits']['crew'])) {
|
|||
|
|
foreach ($movieData['credits']['crew'] as $crew) {
|
|||
|
|
if (isset($crew['job']) && $crew['job'] === 'Director') {
|
|||
|
|
$director = $crew['name'];
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
$data['director'] = $director;
|
|||
|
|
|
|||
|
|
// 6. 类型
|
|||
|
|
$genres = array();
|
|||
|
|
if (isset($movieData['genres'])) {
|
|||
|
|
foreach ($movieData['genres'] as $genre) {
|
|||
|
|
if (isset($genre['name'])) {
|
|||
|
|
$genres[] = $genre['name'];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
$data['genres'] = !empty($genres) ? $genres : array('未知');
|
|||
|
|
|
|||
|
|
// 7. 时长
|
|||
|
|
$data['runtime'] = isset($movieData['runtime']) ? $movieData['runtime'] : 0;
|
|||
|
|
|
|||
|
|
// 8. 评分
|
|||
|
|
$data['vote_average'] = isset($movieData['vote_average']) ? floatval($movieData['vote_average']) : 0;
|
|||
|
|
$data['vote_count'] = isset($movieData['vote_count']) ? intval($movieData['vote_count']) : 0;
|
|||
|
|
|
|||
|
|
// 9. 生产国家
|
|||
|
|
$countries = array();
|
|||
|
|
if (isset($movieData['production_countries'])) {
|
|||
|
|
foreach ($movieData['production_countries'] as $country) {
|
|||
|
|
if (isset($country['name'])) {
|
|||
|
|
$countries[] = $country['name'];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
$data['production_countries'] = !empty($countries) ? $countries : array('未知');
|
|||
|
|
|
|||
|
|
// 10. 初始化影评和自定义字段
|
|||
|
|
$data['review'] = '';
|
|||
|
|
$data['review_updated'] = 0;
|
|||
|
|
|
|||
|
|
// 初始化自定义字段
|
|||
|
|
$data['custom_start_date'] = '';
|
|||
|
|
$data['custom_watch_date'] = '';
|
|||
|
|
$data['custom_watch_method'] = '';
|
|||
|
|
$data['custom_movie_category'] = '';
|
|||
|
|
$data['custom_recommendation'] = 0;
|
|||
|
|
$data['custom_my_rating'] = '';
|
|||
|
|
|
|||
|
|
// 11. 添加抓取时间
|
|||
|
|
$data['fetched_at'] = time();
|
|||
|
|
|
|||
|
|
return $data;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 渲染编辑器按钮 - 增加关键词联想搜索
|
|||
|
|
*/
|
|||
|
|
public static function renderButton()
|
|||
|
|
{
|
|||
|
|
// 获取插件配置
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options')->plugin('MovieInfo');
|
|||
|
|
$apiKey = isset($options->tmdbApiKey) ? $options->tmdbApiKey : '';
|
|||
|
|
$language = isset($options->language) ? $options->language : 'zh-CN';
|
|||
|
|
|
|||
|
|
// 生成nonce或token用于验证请求
|
|||
|
|
$nonce = md5(uniqid());
|
|||
|
|
|
|||
|
|
echo <<<HTML
|
|||
|
|
<style>
|
|||
|
|
#movieinfo-button {
|
|||
|
|
padding: 5px!important;
|
|||
|
|
background: #fff;
|
|||
|
|
cursor: pointer;
|
|||
|
|
color: white;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
font-size: 16px;
|
|||
|
|
line-height: 1;
|
|||
|
|
display: inline-flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
min-width: 26px;
|
|||
|
|
min-height: 26px;
|
|||
|
|
}
|
|||
|
|
#movieinfo-button:hover { background: #E9E9E6 }
|
|||
|
|
.dark #movieinfo-button { background: rgb(16, 25, 40); }
|
|||
|
|
.dark #movieinfo-button:hover { background: #375d85; }
|
|||
|
|
|
|||
|
|
/* 搜索建议下拉框样式 */
|
|||
|
|
.movie-search-suggestions {
|
|||
|
|
position: absolute;
|
|||
|
|
background: white;
|
|||
|
|
border: 1px solid #ddd;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|||
|
|
max-height: 300px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
z-index: 10001;
|
|||
|
|
width: 300px;
|
|||
|
|
margin-top: 2px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-suggestion-item {
|
|||
|
|
padding: 8px 12px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
border-bottom: 1px solid #f0f0f0;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #333;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-suggestion-item:hover {
|
|||
|
|
background: #f5f5f5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-suggestion-item.selected {
|
|||
|
|
background: #0073aa;
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-suggestion-poster {
|
|||
|
|
width: 30px;
|
|||
|
|
height: 45px;
|
|||
|
|
object-fit: cover;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-suggestion-info {
|
|||
|
|
flex-grow: 1;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-suggestion-title {
|
|||
|
|
font-weight: 600;
|
|||
|
|
margin-bottom: 2px;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-suggestion-year {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-suggestion-item:hover .movie-search-suggestion-year {
|
|||
|
|
color: #ccc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-suggestion-item.selected .movie-search-suggestion-year {
|
|||
|
|
color: #ccc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-loading {
|
|||
|
|
padding: 10px;
|
|||
|
|
text-align: center;
|
|||
|
|
color: #666;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-no-results {
|
|||
|
|
padding: 10px;
|
|||
|
|
text-align: center;
|
|||
|
|
color: #999;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-input-container {
|
|||
|
|
position: relative;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-search-results {
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.movie-id-section {
|
|||
|
|
margin-top: 15px;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
padding-top: 15px;
|
|||
|
|
border-top: 1px solid #eee;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark .movie-search-suggestions {
|
|||
|
|
background: #2d2d2d;
|
|||
|
|
border-color: #404040;
|
|||
|
|
color: #e0e0e0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark .movie-search-suggestion-item {
|
|||
|
|
color: #e0e0e0;
|
|||
|
|
border-bottom-color: #404040;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark .movie-search-suggestion-item:hover {
|
|||
|
|
background: #3a3a3a;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark .movie-search-suggestion-item.selected {
|
|||
|
|
background: #0073aa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark .movie-search-suggestion-year {
|
|||
|
|
color: #aaa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark .movie-search-loading,
|
|||
|
|
.dark .movie-search-no-results {
|
|||
|
|
color: #aaa;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
(function() {
|
|||
|
|
function initMovieButton() {
|
|||
|
|
var toolbar = null;
|
|||
|
|
var selectors = ['.wmd-button-row', '.typecho-post-option', '.submit', '#custom-field'];
|
|||
|
|
for (var i = 0; i < selectors.length; i++) {
|
|||
|
|
var el = document.querySelector(selectors[i]);
|
|||
|
|
if (el) { toolbar = el; break; }
|
|||
|
|
}
|
|||
|
|
if (!toolbar) {
|
|||
|
|
setTimeout(initMovieButton, 500);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (document.getElementById('movieinfo-button')) return;
|
|||
|
|
|
|||
|
|
var button = document.createElement('button');
|
|||
|
|
button.type = 'button';
|
|||
|
|
button.id = 'movieinfo-button';
|
|||
|
|
button.className = 'btn btn-s';
|
|||
|
|
button.innerHTML = '🎬';
|
|||
|
|
button.style.cssText = 'padding:6px 12px;';
|
|||
|
|
toolbar.appendChild(button);
|
|||
|
|
|
|||
|
|
button.addEventListener('click', function() {
|
|||
|
|
showMovieDialog();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TMDB搜索函数
|
|||
|
|
function searchMovies(keyword, callback) {
|
|||
|
|
if (!keyword || keyword.length < 2) {
|
|||
|
|
callback([]);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用插件配置的API Key和语言
|
|||
|
|
var apiKey = '{$apiKey}';
|
|||
|
|
var language = '{$language}';
|
|||
|
|
|
|||
|
|
if (!apiKey) {
|
|||
|
|
alert('请先在插件设置中配置TMDB API Key');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 发送AJAX请求
|
|||
|
|
var xhr = new XMLHttpRequest();
|
|||
|
|
var url = 'https://api.themoviedb.org/3/search/movie?api_key=' + encodeURIComponent(apiKey) +
|
|||
|
|
'&language=' + encodeURIComponent(language) +
|
|||
|
|
'&query=' + encodeURIComponent(keyword) +
|
|||
|
|
'&page=1&include_adult=false';
|
|||
|
|
|
|||
|
|
xhr.open('GET', url, true);
|
|||
|
|
xhr.onreadystatechange = function() {
|
|||
|
|
if (xhr.readyState === 4) {
|
|||
|
|
if (xhr.status === 200) {
|
|||
|
|
try {
|
|||
|
|
var response = JSON.parse(xhr.responseText);
|
|||
|
|
if (response.results && response.results.length > 0) {
|
|||
|
|
callback(response.results.slice(0, 10)); // 只返回前10个结果
|
|||
|
|
} else {
|
|||
|
|
callback([]);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('解析JSON失败:', e);
|
|||
|
|
callback([]);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
console.error('搜索失败:', xhr.status);
|
|||
|
|
callback([]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
xhr.onerror = function() {
|
|||
|
|
callback([]);
|
|||
|
|
};
|
|||
|
|
xhr.send();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示搜索建议
|
|||
|
|
function showSearchSuggestions(keyword, suggestions, container, inputField) {
|
|||
|
|
// 移除现有的建议框
|
|||
|
|
var existing = document.getElementById('movie-search-suggestions');
|
|||
|
|
if (existing) {
|
|||
|
|
existing.remove();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!suggestions || suggestions.length === 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建建议框
|
|||
|
|
var suggestionsDiv = document.createElement('div');
|
|||
|
|
suggestionsDiv.id = 'movie-search-suggestions';
|
|||
|
|
suggestionsDiv.className = 'movie-search-suggestions';
|
|||
|
|
|
|||
|
|
suggestions.forEach(function(movie, index) {
|
|||
|
|
var item = document.createElement('div');
|
|||
|
|
item.className = 'movie-search-suggestion-item';
|
|||
|
|
item.dataset.movieId = movie.id;
|
|||
|
|
item.dataset.movieTitle = movie.title;
|
|||
|
|
item.dataset.movieYear = movie.release_date ? movie.release_date.substring(0, 4) : '未知';
|
|||
|
|
|
|||
|
|
// 海报URL
|
|||
|
|
var posterUrl = movie.poster_path ?
|
|||
|
|
'https://image.tmdb.org/t/p/w92' + movie.poster_path : '';
|
|||
|
|
|
|||
|
|
item.innerHTML =
|
|||
|
|
(posterUrl ?
|
|||
|
|
'<img src="' + posterUrl + '" class="movie-search-suggestion-poster" alt="' + movie.title + '" onerror="this.style.display=\'none\'">' :
|
|||
|
|
'<div class="movie-search-suggestion-poster" style="background:#f0f0f0; display:flex; align-items:center; justify-content:center; color:#999; font-size:12px;">无</div>') +
|
|||
|
|
'<div class="movie-search-suggestion-info">' +
|
|||
|
|
'<div class="movie-search-suggestion-title">' + movie.title + '</div>' +
|
|||
|
|
'<div class="movie-search-suggestion-year">' +
|
|||
|
|
(movie.release_date ? movie.release_date.substring(0, 4) : '未知') +
|
|||
|
|
(movie.vote_average ? ' · ⭐ ' + movie.vote_average.toFixed(1) : '') +
|
|||
|
|
'</div>' +
|
|||
|
|
'</div>';
|
|||
|
|
|
|||
|
|
// 点击选择
|
|||
|
|
item.addEventListener('click', function() {
|
|||
|
|
var movieId = this.dataset.movieId;
|
|||
|
|
var movieTitle = this.dataset.movieTitle;
|
|||
|
|
|
|||
|
|
// 设置ID输入框的值
|
|||
|
|
inputField.value = movieId;
|
|||
|
|
|
|||
|
|
// 在输入框下方显示选择提示
|
|||
|
|
var selectionInfo = document.getElementById('movie-search-selection-info');
|
|||
|
|
if (!selectionInfo) {
|
|||
|
|
selectionInfo = document.createElement('div');
|
|||
|
|
selectionInfo.id = 'movie-search-selection-info';
|
|||
|
|
selectionInfo.style.cssText = 'margin-top:5px; font-size:12px; color:#0073aa;';
|
|||
|
|
container.parentNode.insertBefore(selectionInfo, container.nextSibling);
|
|||
|
|
}
|
|||
|
|
selectionInfo.innerHTML = '已选择: <strong>' + movieTitle + '</strong> (ID: ' + movieId + ')';
|
|||
|
|
|
|||
|
|
// 移除建议框
|
|||
|
|
suggestionsDiv.remove();
|
|||
|
|
|
|||
|
|
// 触发输入事件以更新预览
|
|||
|
|
var event = new Event('input', { bubbles: true });
|
|||
|
|
inputField.dispatchEvent(event);
|
|||
|
|
|
|||
|
|
// 聚焦到影评输入框
|
|||
|
|
var reviewInput = document.getElementById('movieinfo-review');
|
|||
|
|
if (reviewInput) {
|
|||
|
|
setTimeout(function() {
|
|||
|
|
reviewInput.focus();
|
|||
|
|
}, 100);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 鼠标悬停效果
|
|||
|
|
item.addEventListener('mouseenter', function() {
|
|||
|
|
this.classList.add('selected');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
item.addEventListener('mouseleave', function() {
|
|||
|
|
this.classList.remove('selected');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
suggestionsDiv.appendChild(item);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加到页面
|
|||
|
|
container.appendChild(suggestionsDiv);
|
|||
|
|
|
|||
|
|
// 点击页面其他地方关闭建议框
|
|||
|
|
setTimeout(function() {
|
|||
|
|
document.addEventListener('click', function closeSuggestions(e) {
|
|||
|
|
if (!container.contains(e.target) && !suggestionsDiv.contains(e.target)) {
|
|||
|
|
suggestionsDiv.remove();
|
|||
|
|
document.removeEventListener('click', closeSuggestions);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}, 10);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function showMovieDialog() {
|
|||
|
|
// 创建遮罩层
|
|||
|
|
var overlay = document.createElement('div');
|
|||
|
|
overlay.style.cssText = 'position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:9999;';
|
|||
|
|
|
|||
|
|
// 创建对话框
|
|||
|
|
var modal = document.createElement('div');
|
|||
|
|
modal.style.cssText = 'position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:white; border:1px solid #ddd; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.15); z-index:10000; width:500px; max-height:90vh; overflow-y:auto; padding:20px;';
|
|||
|
|
|
|||
|
|
modal.innerHTML =
|
|||
|
|
'<!--<h3 style="margin-top:0; color:#000;margin-bottom:15px;">插入TMDB电影</h3>-->' +
|
|||
|
|
|
|||
|
|
'<div class="movie-search-input-container" style="margin-bottom:10px;margin-top:-5px;">' +
|
|||
|
|
'<label style="display:block; margin-bottom:5px;color:#000; font-weight:600;">🔍 搜索电影</label>' +
|
|||
|
|
'<input type="text" id="movieinfo-search" placeholder="输入电影名关键词搜索(至少2个字符)..." style="width:100%; padding:8px; border:1px solid #ddd; border-radius:4px; font-size:14px; box-sizing:border-box;">' +
|
|||
|
|
'<div style="font-size:12px; color:#666; margin-top:5px;">输入电影名称,从下拉菜单中选择后自动填写ID</div>' +
|
|||
|
|
'<div class="movie-search-results" style="position:relative;"></div>' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div class="movie-id-section">' +
|
|||
|
|
'<label style="display:block; margin-bottom:5px;color:#000; font-weight:600;">🎬 TMDB电影ID</label>' +
|
|||
|
|
'<input type="text" id="movieinfo-id" placeholder="自动从搜索结果填充,也可手动输入ID" style="width:100%; padding:8px; border:1px solid #ddd; border-radius:4px; font-size:14px; box-sizing:border-box;">' +
|
|||
|
|
'<div style="font-size:12px; color:#666; margin-top:5px;">从搜索结果中选择或手动输入TMDB电影ID数字</div>' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div style="margin-bottom:15px;">' +
|
|||
|
|
'<label style="display:block;color:#000; margin-bottom:5px; font-weight:600;">💭 我的影评</label>' +
|
|||
|
|
'<textarea id="movieinfo-review" placeholder="输入影评或简短评论..." style="width:100%; padding:8px; border:1px solid #ddd; border-radius:4px; font-size:14px; box-sizing:border-box; min-height:80px; resize:vertical;color:#666;background-Color:#fff;"></textarea>' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div style="margin-bottom:15px; padding:15px; background:#f8f9fa; border-radius:6px; border:1px solid #eaeaea;">' +
|
|||
|
|
'<h4 style="margin-top:0; margin-bottom:12px; font-size:15px; color:#333;">📖 我的观看记录(可选)</h4>' +
|
|||
|
|
|
|||
|
|
'<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">' +
|
|||
|
|
'<div>' +
|
|||
|
|
'<label style="display:block; margin-bottom:5px; font-size:13px; color:#555;">开始观看</label>' +
|
|||
|
|
'<input type="text" id="movieinfo-start-date" placeholder="例如:2024-03-15" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;">' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div>' +
|
|||
|
|
'<label style="display:block; margin-bottom:5px; font-size:13px; color:#555;">结束观看</label>' +
|
|||
|
|
'<input type="text" id="movieinfo-watch-date" placeholder="例如:2024-03-15(同一天)或2024-03-20" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;">' +
|
|||
|
|
'</div>' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;margin:10px 0px;">' +
|
|||
|
|
'<div>' +
|
|||
|
|
'<label style="display:block; margin-bottom:5px; font-size:13px; color:#555;">观看方式</label>' +
|
|||
|
|
'<input type="text" id="movieinfo-watch-method" placeholder="例如:电影院" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;">' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div>' +
|
|||
|
|
'<label style="display:block; margin-bottom:5px; font-size:13px; color:#555;">电影分类</label>' +
|
|||
|
|
'<input type="text" id="movieinfo-movie-category" placeholder="例如:科幻" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;">' +
|
|||
|
|
'</div>' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div style="margin-top:12px;">' +
|
|||
|
|
'<label style="display:block; margin-bottom:5px; font-size:13px;color:#666; ">我的评分</label>' +
|
|||
|
|
'<input type="text" id="movieinfo-my-rating" placeholder="例如:8.5/10" style="width:100%; color:#666;padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;background-Color:#fff;">' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div style="margin-top:12px;">' +
|
|||
|
|
'<label style="display:block; margin-bottom:5px; font-size:13px;color:#666; ">推荐指数</label>' +
|
|||
|
|
'<select id="movieinfo-recommendation" style="width:100%; color:#666;padding:6px; border:1px solid #ddd; border-radius:4px; font-size:13px; box-sizing:border-box;background-Color:#fff;">' +
|
|||
|
|
'<option value="0">请选择推荐指数</option>' +
|
|||
|
|
'<option value="1">★ 1星 - 不推荐</option>' +
|
|||
|
|
'<option value="2">★★ 2星 - 一般</option>' +
|
|||
|
|
'<option value="3">★★★ 3星 - 还行</option>' +
|
|||
|
|
'<option value="4">★★★★ 4星 - 推荐</option>' +
|
|||
|
|
'<option value="5">★★★★★ 5星 - 极力推荐</option>' +
|
|||
|
|
'</select>' +
|
|||
|
|
'</div>' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div style="margin-top:15px;margin-bottom:15px; background:#e8f4fd; padding:12px; border-radius:6px; font-size:13px; color:#666; border:1px solid #d1e7ff;">' +
|
|||
|
|
'<div style="font-weight:600; color:#000; margin-bottom:5px;">📝 生成格式预览:</div>' +
|
|||
|
|
'<div style="font-family:monospace; background:#fff; padding:8px; margin-top:5px; border-radius:4px; border:1px solid #cfe2ff; max-height:80px; overflow-y:auto;">' +
|
|||
|
|
'<div id="movieinfo-preview" style="color:#333; word-break:break-all; font-size:13px; line-height:1.4;">[movie:ID:影评]</div>' +
|
|||
|
|
'</div>' +
|
|||
|
|
'<div style="margin-top:8px; color:#666;">说明:自定义信息将自动保存到JSON缓存中</div>' +
|
|||
|
|
'</div>' +
|
|||
|
|
|
|||
|
|
'<div style="text-align:right;">' +
|
|||
|
|
'<button type="button" id="movieinfo-cancel" style="padding:8px 16px; margin-right:10px; background:#f5f5f5; border:1px solid #ddd; border-radius:4px; cursor:pointer; font-size:14px;">取消</button>' +
|
|||
|
|
'<button type="button" id="movieinfo-insert" style="padding:8px 20px; background:#01d277; color:white; border:none; border-radius:4px; cursor:pointer; font-size:14px; font-weight:600;">插入</button>' +
|
|||
|
|
'</div>';
|
|||
|
|
|
|||
|
|
// 添加到页面
|
|||
|
|
document.body.appendChild(overlay);
|
|||
|
|
document.body.appendChild(modal);
|
|||
|
|
|
|||
|
|
// 获取DOM元素
|
|||
|
|
var searchInput = document.getElementById('movieinfo-search');
|
|||
|
|
var resultsContainer = document.querySelector('.movie-search-results');
|
|||
|
|
var movieIdInput = document.getElementById('movieinfo-id');
|
|||
|
|
var reviewInput = document.getElementById('movieinfo-review');
|
|||
|
|
var startDateInput = document.getElementById('movieinfo-start-date');
|
|||
|
|
var watchDateInput = document.getElementById('movieinfo-watch-date');
|
|||
|
|
var watchMethodInput = document.getElementById('movieinfo-watch-method');
|
|||
|
|
var movieCategoryInput = document.getElementById('movieinfo-movie-category');
|
|||
|
|
var recommendationSelect = document.getElementById('movieinfo-recommendation');
|
|||
|
|
var myRatingInput = document.getElementById('movieinfo-my-rating');
|
|||
|
|
var previewDiv = document.getElementById('movieinfo-preview');
|
|||
|
|
var insertBtn = document.getElementById('movieinfo-insert');
|
|||
|
|
var cancelBtn = document.getElementById('movieinfo-cancel');
|
|||
|
|
|
|||
|
|
// 搜索输入防抖处理
|
|||
|
|
var searchTimeout;
|
|||
|
|
searchInput.addEventListener('input', function() {
|
|||
|
|
clearTimeout(searchTimeout);
|
|||
|
|
var keyword = this.value.trim();
|
|||
|
|
|
|||
|
|
if (keyword.length < 2) {
|
|||
|
|
// 移除现有的建议框
|
|||
|
|
var existing = document.getElementById('movie-search-suggestions');
|
|||
|
|
if (existing) existing.remove();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示加载中
|
|||
|
|
var existing = document.getElementById('movie-search-suggestions');
|
|||
|
|
if (existing) existing.remove();
|
|||
|
|
|
|||
|
|
var loadingDiv = document.createElement('div');
|
|||
|
|
loadingDiv.id = 'movie-search-suggestions';
|
|||
|
|
loadingDiv.className = 'movie-search-suggestions';
|
|||
|
|
loadingDiv.innerHTML = '<div class="movie-search-loading">搜索中...</div>';
|
|||
|
|
resultsContainer.appendChild(loadingDiv);
|
|||
|
|
|
|||
|
|
searchTimeout = setTimeout(function() {
|
|||
|
|
searchMovies(keyword, function(suggestions) {
|
|||
|
|
// 移除加载中
|
|||
|
|
var loading = document.getElementById('movie-search-suggestions');
|
|||
|
|
if (loading) loading.remove();
|
|||
|
|
|
|||
|
|
if (suggestions.length === 0) {
|
|||
|
|
var noResultsDiv = document.createElement('div');
|
|||
|
|
noResultsDiv.id = 'movie-search-suggestions';
|
|||
|
|
noResultsDiv.className = 'movie-search-suggestions';
|
|||
|
|
noResultsDiv.innerHTML = '<div class="movie-search-no-results">未找到相关电影,请尝试其他关键词</div>';
|
|||
|
|
resultsContainer.appendChild(noResultsDiv);
|
|||
|
|
} else {
|
|||
|
|
showSearchSuggestions(keyword, suggestions, resultsContainer, movieIdInput);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}, 300); // 300ms防抖延迟
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 搜索输入框键盘导航
|
|||
|
|
var selectedIndex = -1;
|
|||
|
|
searchInput.addEventListener('keydown', function(e) {
|
|||
|
|
var suggestionsDiv = document.getElementById('movie-search-suggestions');
|
|||
|
|
if (!suggestionsDiv) return;
|
|||
|
|
|
|||
|
|
var items = suggestionsDiv.querySelectorAll('.movie-search-suggestion-item');
|
|||
|
|
if (items.length === 0) return;
|
|||
|
|
|
|||
|
|
if (e.key === 'ArrowDown') {
|
|||
|
|
e.preventDefault();
|
|||
|
|
selectedIndex = (selectedIndex + 1) % items.length;
|
|||
|
|
updateSelection(items);
|
|||
|
|
} else if (e.key === 'ArrowUp') {
|
|||
|
|
e.preventDefault();
|
|||
|
|
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|||
|
|
updateSelection(items);
|
|||
|
|
} else if (e.key === 'Enter') {
|
|||
|
|
e.preventDefault();
|
|||
|
|
if (selectedIndex >= 0 && selectedIndex < items.length) {
|
|||
|
|
items[selectedIndex].click();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
function updateSelection(items) {
|
|||
|
|
items.forEach(function(item, index) {
|
|||
|
|
if (index === selectedIndex) {
|
|||
|
|
item.classList.add('selected');
|
|||
|
|
item.scrollIntoView({ block: 'nearest' });
|
|||
|
|
} else {
|
|||
|
|
item.classList.remove('selected');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新预览
|
|||
|
|
function updatePreview() {
|
|||
|
|
var movieId = movieIdInput.value.trim();
|
|||
|
|
var review = reviewInput.value.trim();
|
|||
|
|
var startDate = startDateInput.value.trim();
|
|||
|
|
var watchDate = watchDateInput.value.trim();
|
|||
|
|
var watchMethod = watchMethodInput.value.trim();
|
|||
|
|
var movieCategory = movieCategoryInput.value.trim();
|
|||
|
|
var recommendation = parseInt(recommendationSelect.value) || 0;
|
|||
|
|
var myRating = myRatingInput.value.trim();
|
|||
|
|
|
|||
|
|
var preview = '[movie:' + (movieId || 'ID');
|
|||
|
|
|
|||
|
|
// 构建自定义数据对象
|
|||
|
|
var customData = {};
|
|||
|
|
if (startDate) customData.startDate = startDate;
|
|||
|
|
if (watchDate) customData.watchDate = watchDate;
|
|||
|
|
if (watchMethod) customData.watchMethod = watchMethod;
|
|||
|
|
if (movieCategory) customData.movieCategory = movieCategory;
|
|||
|
|
if (recommendation > 0) customData.recommendation = recommendation;
|
|||
|
|
if (myRating) customData.myRating = myRating;
|
|||
|
|
|
|||
|
|
// 显示预览(中文显示)
|
|||
|
|
var displayText = '';
|
|||
|
|
|
|||
|
|
if (review) {
|
|||
|
|
displayText = review;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果有自定义数据,添加到显示文本
|
|||
|
|
if (Object.keys(customData).length > 0) {
|
|||
|
|
var customText = '(自定义:';
|
|||
|
|
var customParts = [];
|
|||
|
|
if (startDate) customParts.push('开始观看:' + startDate);
|
|||
|
|
if (watchDate) customParts.push('结束观看:' + watchDate);
|
|||
|
|
if (watchMethod) customParts.push('观看方式:' + watchMethod);
|
|||
|
|
if (movieCategory) customParts.push('电影分类:' + movieCategory);
|
|||
|
|
if (myRating) customParts.push('我的评分:' + myRating);
|
|||
|
|
if (recommendation > 0) customParts.push('推荐指数:' + '★'.repeat(recommendation));
|
|||
|
|
|
|||
|
|
customText += customParts.join(',') + ')';
|
|||
|
|
|
|||
|
|
if (displayText) {
|
|||
|
|
displayText += customText;
|
|||
|
|
} else {
|
|||
|
|
displayText = customText;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果有内容,添加到预览
|
|||
|
|
if (displayText) {
|
|||
|
|
preview += ':' + displayText;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
preview += ']';
|
|||
|
|
|
|||
|
|
previewDiv.innerHTML = preview;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 绑定输入事件
|
|||
|
|
movieIdInput.addEventListener('input', updatePreview);
|
|||
|
|
reviewInput.addEventListener('input', updatePreview);
|
|||
|
|
startDateInput.addEventListener('input', updatePreview);
|
|||
|
|
watchDateInput.addEventListener('input', updatePreview);
|
|||
|
|
watchMethodInput.addEventListener('input', updatePreview);
|
|||
|
|
movieCategoryInput.addEventListener('input', updatePreview);
|
|||
|
|
recommendationSelect.addEventListener('change', updatePreview);
|
|||
|
|
myRatingInput.addEventListener('input', updatePreview);
|
|||
|
|
|
|||
|
|
// 聚焦搜索框
|
|||
|
|
setTimeout(function() {
|
|||
|
|
searchInput.focus();
|
|||
|
|
}, 100);
|
|||
|
|
|
|||
|
|
// 插入按钮点击
|
|||
|
|
insertBtn.addEventListener('click', function() {
|
|||
|
|
var movieId = movieIdInput.value.trim();
|
|||
|
|
var review = reviewInput.value.trim();
|
|||
|
|
var startDate = startDateInput.value.trim();
|
|||
|
|
var watchDate = watchDateInput.value.trim();
|
|||
|
|
var watchMethod = watchMethodInput.value.trim();
|
|||
|
|
var movieCategory = movieCategoryInput.value.trim();
|
|||
|
|
var recommendation = parseInt(recommendationSelect.value) || 0;
|
|||
|
|
var myRating = myRatingInput.value.trim();
|
|||
|
|
|
|||
|
|
if (movieId && /^\d+$/.test(movieId)) {
|
|||
|
|
// 构建自定义数据对象
|
|||
|
|
var customData = {};
|
|||
|
|
if (startDate) customData.startDate = startDate;
|
|||
|
|
if (watchDate) customData.watchDate = watchDate;
|
|||
|
|
if (watchMethod) customData.watchMethod = watchMethod;
|
|||
|
|
if (movieCategory) customData.movieCategory = movieCategory;
|
|||
|
|
if (recommendation > 0) customData.recommendation = recommendation;
|
|||
|
|
if (myRating) customData.myRating = myRating;
|
|||
|
|
|
|||
|
|
// 构建短代码
|
|||
|
|
var shortcode = '[movie:' + movieId;
|
|||
|
|
|
|||
|
|
// 如果有影评或自定义数据
|
|||
|
|
if (review || Object.keys(customData).length > 0) {
|
|||
|
|
var content = '';
|
|||
|
|
|
|||
|
|
// 处理影评部分
|
|||
|
|
if (review) {
|
|||
|
|
// 对影评中的方括号进行编码
|
|||
|
|
var encodedReview = review.replace(/[\[\]]/g, function(match) {
|
|||
|
|
if (match === '[') return '[';
|
|||
|
|
if (match === ']') return ']';
|
|||
|
|
return match;
|
|||
|
|
});
|
|||
|
|
content = encodedReview;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果有自定义数据,添加到内容后面
|
|||
|
|
if (Object.keys(customData).length > 0) {
|
|||
|
|
// 将自定义数据转为JSON并编码
|
|||
|
|
var customJson = encodeURIComponent(JSON.stringify(customData));
|
|||
|
|
if (content) {
|
|||
|
|
content += '|CUSTOM:' + customJson;
|
|||
|
|
} else {
|
|||
|
|
content = '|CUSTOM:' + customJson;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
shortcode += ':' + content;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
shortcode += ']';
|
|||
|
|
|
|||
|
|
// 在编辑器中显示中文
|
|||
|
|
var displayShortcode = '[movie:' + movieId;
|
|||
|
|
if (review || Object.keys(customData).length > 0) {
|
|||
|
|
var displayContent = review;
|
|||
|
|
if (Object.keys(customData).length > 0) {
|
|||
|
|
var displayCustomText = '(自定义:';
|
|||
|
|
var customParts = [];
|
|||
|
|
if (startDate) customParts.push('开始观看:' + startDate);
|
|||
|
|
if (watchDate) customParts.push('结束观看:' + watchDate);
|
|||
|
|
if (watchMethod) customParts.push('观看方式:' + watchMethod);
|
|||
|
|
if (movieCategory) customParts.push('电影分类:' + movieCategory);
|
|||
|
|
if (myRating) customParts.push('我的评分:' + myRating);
|
|||
|
|
if (recommendation > 0) customParts.push('推荐指数:' + '★'.repeat(recommendation));
|
|||
|
|
|
|||
|
|
displayCustomText += customParts.join(',') + ')';
|
|||
|
|
|
|||
|
|
if (displayContent) {
|
|||
|
|
displayContent += displayCustomText;
|
|||
|
|
} else {
|
|||
|
|
displayContent = displayCustomText;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
displayShortcode += ':' + displayContent;
|
|||
|
|
}
|
|||
|
|
displayShortcode += ']';
|
|||
|
|
|
|||
|
|
insertMovieShortcode(displayShortcode);
|
|||
|
|
closeDialog();
|
|||
|
|
} else if (movieId) {
|
|||
|
|
alert('请输入有效的电影ID(纯数字)');
|
|||
|
|
} else {
|
|||
|
|
alert('请输入TMDB电影ID');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 取消按钮点击
|
|||
|
|
cancelBtn.addEventListener('click', closeDialog);
|
|||
|
|
|
|||
|
|
// 点击遮罩层关闭
|
|||
|
|
overlay.addEventListener('click', closeDialog);
|
|||
|
|
|
|||
|
|
// ID输入框键盘事件
|
|||
|
|
movieIdInput.addEventListener('keydown', function(e) {
|
|||
|
|
if (e.key === 'Enter') {
|
|||
|
|
e.preventDefault();
|
|||
|
|
insertBtn.click();
|
|||
|
|
} else if (e.key === 'Escape') {
|
|||
|
|
closeDialog();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
reviewInput.addEventListener('keydown', function(e) {
|
|||
|
|
if (e.key === 'Enter' && e.ctrlKey) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
insertBtn.click();
|
|||
|
|
} else if (e.key === 'Escape') {
|
|||
|
|
closeDialog();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 关闭对话框
|
|||
|
|
function closeDialog() {
|
|||
|
|
if (overlay.parentNode) document.body.removeChild(overlay);
|
|||
|
|
if (modal.parentNode) document.body.removeChild(modal);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 插入短代码到编辑器
|
|||
|
|
function insertMovieShortcode(shortcode) {
|
|||
|
|
var textarea = document.getElementById('text');
|
|||
|
|
if (!textarea) return;
|
|||
|
|
|
|||
|
|
var start = textarea.selectionStart;
|
|||
|
|
var end = textarea.selectionEnd;
|
|||
|
|
var text = textarea.value;
|
|||
|
|
|
|||
|
|
// 直接插入中文短代码到编辑器
|
|||
|
|
textarea.value = text.substring(0, start) + shortcode + text.substring(end);
|
|||
|
|
textarea.selectionStart = textarea.selectionEnd = start + shortcode.length;
|
|||
|
|
textarea.focus();
|
|||
|
|
|
|||
|
|
// 触发输入事件
|
|||
|
|
var event = new Event('input', { bubbles: true });
|
|||
|
|
textarea.dispatchEvent(event);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
if (document.readyState === 'loading') {
|
|||
|
|
document.addEventListener('DOMContentLoaded', initMovieButton);
|
|||
|
|
} else {
|
|||
|
|
initMovieButton();
|
|||
|
|
}
|
|||
|
|
})();
|
|||
|
|
</script>
|
|||
|
|
HTML;
|
|||
|
|
}
|
|||
|
|
}
|