v10: 修影院编辑回填布尔化 bug;评分改 10 星制;观影人改弹窗+拼音首字母联想;豆瓣自动补充迁移(movie_details.douban_meta,schema v10)

This commit is contained in:
2026-10-01 10:21:45 +08:00
parent 746f02581e
commit 2c36335c3e
8 changed files with 1050 additions and 39 deletions
+72 -9
View File
@@ -10,6 +10,14 @@
$canWrite = \App\Auth::canWrite();
$hasPoster = !empty($m['poster_key']);
$hasTicket = !empty($m['ticket_key']);
// 豆瓣元数据(v10):整体 JSON 存 douban_meta,随用随解;解不开就当没有
$doubanMeta = [];
if (!empty($m['douban_meta'])) {
$decoded = json_decode((string)$m['douban_meta'], true);
if (is_array($decoded)) {
$doubanMeta = $decoded;
}
}
$watchTxt = \App\Movie::formatWatch($m['starts_at'], $m['ends_at'] ?? null);
$fmtNum = static function ($v): string {
if ($v === null) {
@@ -57,6 +65,16 @@ $feelTags = static function (array $keys) use ($feelCat) {
<?php return ob_get_clean();
};
/** 静态 10 星展示(v10):左 n 颗实心金黄,其余空心灰;n 越界按 0/10 兜底 */
$starsHtml = static function (int $n): string {
$n = max(0, min(10, $n));
$out = '<span class="stars-disp" aria-hidden="true">';
for ($i = 1; $i <= 10; $i++) {
$out .= '<span class="' . ($i <= $n ? 'd-on' : 'd-off') . '">' . ($i <= $n ? '★' : '☆') . '</span>';
}
return $out . '</span>';
};
/** 我的评价表单(打分 + 正文 + 感受),details 按需展开;写评价与修改共用这一份 */
$rvForm = static function (string $summary, bool $open) use ($m, $myReview, $feelPick, $rvError) {
ob_start(); ?>
@@ -67,12 +85,14 @@ $rvForm = static function (string $summary, bool $open) use ($m, $myReview, $fee
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
<div class="rv-rating-row">
<span class="rv-rating-label">我的评分</span>
<span class="rv-scale" role="radiogroup" aria-label="评分,1 分到 10 分">
<?php /* v10:10 星制。DOM 倒序(10..1)+ CSS row-reverse,视觉 1..10 从左到右;
点亮由 JS 加 .lit(含悬停预览), radios 保持 name="rating" 原语义。 */ ?>
<span class="rv-scale rv-stars no-js" role="radiogroup" aria-label="评分,1 分到 10 分">
<?php $myRating = (int)($myReview['rating'] ?? 0); ?>
<?php for ($i = 1; $i <= 10; $i++): ?>
<label class="rv-num" title="<?= $i ?> 分">
<?php for ($i = 10; $i >= 1; $i--): ?>
<label class="rv-star" data-v="<?= $i ?>" title="<?= $i ?> 分 · <?= e(\App\MovieReview::ratingWord($i)) ?>">
<input type="radio" name="rating" value="<?= $i ?>"<?= $myRating === $i ? ' checked' : '' ?>>
<span><?= $i ?></span>
<span class="st-glyph st-off" aria-hidden="true">☆</span><span class="st-glyph st-on" aria-hidden="true">★</span>
</label>
<?php endfor; ?>
</span>
@@ -157,6 +177,30 @@ $commentBox = static function (int $reviewId, int $parentId, string $summary) us
<?php endif; ?>
<span>👥 观影人 <?= count($m['attendees']) ?> 人</span>
</div>
<?php if ($doubanMeta): ?>
<div class="db-meta">
<?php if (!empty($doubanMeta['rating'])): ?>
<span class="db-rating" title="豆瓣评分">豆瓣 <b><?= e((string)$doubanMeta['rating']) ?></b></span>
<?php endif; ?>
<?php foreach ([
'年份' => (string)($doubanMeta['year'] ?? ''),
'类型' => (string)($doubanMeta['genre'] ?? ''),
'片长' => (string)($doubanMeta['runtime'] ?? ''),
'导演' => (string)($doubanMeta['director'] ?? ''),
'主演' => (string)($doubanMeta['actors'] ?? ''),
'制片国家/地区' => (string)($doubanMeta['country'] ?? ''),
'又名' => (string)($doubanMeta['alias'] ?? ''),
] as $dbLbl => $dbVal): ?>
<?php if ($dbVal !== ''): ?>
<span class="db-line"><span class="db-label"><?= $dbLbl ?></span><?= e($dbVal) ?></span>
<?php endif; ?>
<?php endforeach; ?>
<?php if (!empty($doubanMeta['douban_url'])): ?>
<a class="db-link" href="<?= e((string)$doubanMeta['douban_url']) ?>"
target="_blank" rel="noopener noreferrer">在豆瓣查看 ↗</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($m['info'])): ?>
<div class="info-text"><?= nl2br(e($m['info'])) ?></div>
<?php endif; ?>
@@ -277,7 +321,7 @@ $commentBox = static function (int $reviewId, int $parentId, string $summary) us
<?php if ($rvMine): ?><span class="rv-mine">我</span><?php endif; ?>
<span class="rv-time"><?= e(substr((string)$rv['created_at'], 0, 16)) ?></span>
</span>
<span class="rv-score" title="<?= e(\App\MovieReview::ratingWord($rvRate)) ?>"><?= $rvRate ?><span class="rv-score-max">/10</span></span>
<span class="rv-score" title="<?= e(\App\MovieReview::ratingWord($rvRate)) ?>"><?= $starsHtml($rvRate) ?><span class="rv-score-max"><?= $rvRate ?>/10</span></span>
<?php if ($rvMine && $canWrite): ?>
<form method="post" action="<?= e(url('movies/review_del')) ?>" class="rv-del"
onsubmit="return confirm('删除我的这条评价?下面的评论也会一起删除,不可恢复。')">
@@ -466,23 +510,42 @@ $commentBox = static function (int $reviewId, int $parentId, string $summary) us
<?php endif; ?>
<?php if ($canWrite): ?>
<!-- 打分实时提示「几分 · 怎么样」;纯增强,radio 没 JS 也能选 -->
<!-- 打分:星星点亮 + 悬停预览 + 实时提示「几分 · 怎么样」;radio 没 JS 也能选(:has 兜底点亮) -->
<script>
(function () {
var form = document.getElementById('reviewForm');
var word = document.getElementById('rvWord');
if (!form || !word) return;
var scale = form.querySelector('.rv-stars');
if (scale) scale.classList.remove('no-js');
var WORDS = <?= json_encode(\App\MovieReview::ratingWords(), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
function sync() {
function pickedV() {
var picked = form.querySelector('input[name="rating"]:checked');
if (!picked) { word.textContent = '未评分'; word.classList.remove('is-set'); return; }
var v = parseInt(picked.value, 10);
return picked ? parseInt(picked.value, 10) : 0;
}
function paint(v) {
if (!scale) return;
Array.prototype.forEach.call(scale.querySelectorAll('.rv-star'), function (el) {
el.classList.toggle('lit', parseInt(el.getAttribute('data-v'), 10) <= v);
});
}
function sync() {
var v = pickedV();
if (!v) { word.textContent = '未评分'; word.classList.remove('is-set'); paint(0); return; }
word.textContent = v + ' 分 · ' + (WORDS[v - 1] || '');
word.classList.add('is-set');
paint(v);
}
Array.prototype.forEach.call(form.querySelectorAll('input[name="rating"]'), function (el) {
el.addEventListener('change', sync);
});
if (scale) {
scale.addEventListener('mouseover', function (e) {
var lab = e.target.closest ? e.target.closest('.rv-star') : null;
if (lab) paint(parseInt(lab.getAttribute('data-v'), 10));
});
scale.addEventListener('mouseleave', function () { paint(pickedV()); });
}
sync();
})();
</script>