v9: 观影对齐 MovieLog —— 瀑布流/影院管理/评价评论/起止时间
- events 起止时间精确到秒(starts_at DATE→DATETIME + ends_at),_datetime.php 复用 - 影院独立成表(cinemas,地方/名称/地址/主题色 + 50 色板),movie_details 归一为 cinema_id - 评价模型 MovieLog 同款:movie_reviews 每账号每片一条(1-10 分+正文+感受 key), movie_review_comments 两级平铺评论(回复的回复规范化到顶层、跨评价回复拦截) - 感受目录迁到 MovieReview::CATALOG(12 种,Emoji 11.0 老码位) - 观影列表改瀑布流(CSS grid 海报墙),详情页五大块 + 灯箱 + 评价区 - 升级路径:v8 老数据自动归一(影院三段按 (family,place,name) 去重建行、 评价回填、中文感受反查英文 key),回填全部幂等;老库补 cinema_id 列+索引+外键 - 修三 bug:建表顺序(cinemas 必须在 movie_details 前,否则全新安装 1215)、 layout 导航用键名当路由(影院链接指向 r=cinemas)、 _datetime.php 分钟循环变量 $m 覆盖宿主模板的观影记录(编辑页简介/备注回填被清空)
This commit is contained in:
+432
-77
@@ -1,25 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* 观影详情页。
|
||||
* 观影详情页(v9,展示方式照搬 MovieLog 的 detail.php)。
|
||||
*
|
||||
* 海报 / 票根 / 照片一律走 media_url() 代理(全站唯一出口,见 helpers.php);
|
||||
* 感受标签是迁移时已翻成中文的 JSON 数组,这里只展示不加工。
|
||||
* 访客(只读)看不到编辑/删除/传照片那些表单 —— 与名册同一口径。
|
||||
* 五大块:英雄区(左海报右信息)→ 一起看的人 → 电影院信息 → 影像记录(灯箱)→ 评价与评论。
|
||||
* 海报/票根/照片一律走 media_url() 代理;感受标签只存 key,emoji 与文案查 CATALOG。
|
||||
* 访客(只读)看不到编辑/删除/传照片/评价表单 —— 与名册同一口径。
|
||||
* 评价人是「登录账号背后的人」,与同行观影人无关(MovieLog 同款规则)。
|
||||
*/
|
||||
$canWrite = \App\Auth::canWrite();
|
||||
$feel = [];
|
||||
if (!empty($m['feelings'])) {
|
||||
$feel = json_decode((string)$m['feelings'], true);
|
||||
if (!is_array($feel)) {
|
||||
$feel = [];
|
||||
}
|
||||
}
|
||||
$hasPoster = !empty($m['poster_key']);
|
||||
$hasTicket = !empty($m['ticket_key']);
|
||||
$watchTxt = \App\Movie::formatWatch($m['starts_at'], $m['ends_at'] ?? null);
|
||||
$fmtNum = static function ($v): string {
|
||||
if ($v === null) {
|
||||
return '';
|
||||
}
|
||||
return rtrim(rtrim(number_format((float)$v, 1, '.', ''), '0'), '.');
|
||||
};
|
||||
|
||||
/** 头像或首字占位(persons.avatar 是存储相对键) */
|
||||
$avatarHtml = static function (?string $avatar, string $name, string $cls = '') {
|
||||
if ($avatar !== null && $avatar !== '') {
|
||||
return '<img class="rv-avatar ' . e($cls) . '" src="' . e(media_url($avatar)) . '" alt="">';
|
||||
}
|
||||
return '<span class="rv-avatar rv-avatar-text ' . e($cls) . '">' . e(mb_substr($name, 0, 1, 'UTF-8')) . '</span>';
|
||||
};
|
||||
|
||||
/** 可勾选的感受 chips(原生 checkbox,外观在 .feel-inner 上) */
|
||||
$feelPick = static function (array $checked = []) use ($feelCat) {
|
||||
ob_start(); ?>
|
||||
<span class="feel-chips">
|
||||
<?php foreach ($feelCat as $k => $f): ?>
|
||||
<label class="feel-chip feel-pick" title="<?= e($f['label']) ?>">
|
||||
<input type="checkbox" name="feelings[]" value="<?= e($k) ?>"<?= in_array($k, $checked, true) ? ' checked' : '' ?>>
|
||||
<span class="feel-inner"><span class="feel-emoji"><?= $f['emoji'] ?></span><span class="feel-label"><?= e($f['label']) ?></span></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</span>
|
||||
<?php return ob_get_clean();
|
||||
};
|
||||
|
||||
/** 只读的感受标签 */
|
||||
$feelTags = static function (array $keys) use ($feelCat) {
|
||||
if (!$keys) {
|
||||
return '';
|
||||
}
|
||||
ob_start(); ?>
|
||||
<span class="rv-feelings">
|
||||
<?php foreach ($keys as $k): ?>
|
||||
<?php if (!isset($feelCat[$k])) { continue; } $f = $feelCat[$k]; ?>
|
||||
<span class="feel-chip is-static" title="观后感受:<?= e($f['label']) ?>">
|
||||
<span class="feel-emoji"><?= $f['emoji'] ?></span><span class="feel-label"><?= e($f['label']) ?></span>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</span>
|
||||
<?php return ob_get_clean();
|
||||
};
|
||||
|
||||
/** 我的评价表单(打分 + 正文 + 感受),details 按需展开;写评价与修改共用这一份 */
|
||||
$rvForm = static function (string $summary, bool $open) use ($m, $myReview, $feelPick, $rvError) {
|
||||
ob_start(); ?>
|
||||
<details class="rv-composer"<?= $open ? ' open' : '' ?>>
|
||||
<summary class="rv-open-btn"><?= e($summary) ?></summary>
|
||||
<form method="post" action="<?= e(url('movies/review_save')) ?>" class="rv-form" id="reviewForm">
|
||||
<?= csrf_field() ?>
|
||||
<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 $myRating = (int)($myReview['rating'] ?? 0); ?>
|
||||
<?php for ($i = 1; $i <= 10; $i++): ?>
|
||||
<label class="rv-num" title="<?= $i ?> 分">
|
||||
<input type="radio" name="rating" value="<?= $i ?>"<?= $myRating === $i ? ' checked' : '' ?>>
|
||||
<span><?= $i ?></span>
|
||||
</label>
|
||||
<?php endfor; ?>
|
||||
</span>
|
||||
<span class="rv-word<?= $myRating > 0 ? ' is-set' : '' ?>" id="rvWord">
|
||||
<?= $myRating > 0 ? $myRating . ' 分 · ' . e(\App\MovieReview::ratingWord($myRating)) : '未评分' ?>
|
||||
</span>
|
||||
</div>
|
||||
<textarea name="content" class="rv-textarea" maxlength="<?= (int)\App\MovieReview::CONTENT_MAX ?>" required
|
||||
placeholder="这部电影怎么样?剧情、表演、画面、音效……随便聊聊"><?= e((string)($myReview['content'] ?? '')) ?></textarea>
|
||||
<div class="rv-feeling-pick">
|
||||
<span class="rv-pick-label">我看完的感受(可多选)</span>
|
||||
<?= $feelPick((array)($myReview['feelings'] ?? [])) ?>
|
||||
</div>
|
||||
<div class="rv-form-foot">
|
||||
<span class="rv-hint">每个账号对每部电影只有一条评价,再次提交即为修改。</span>
|
||||
<button type="submit" class="btn btn-sm"><?= $myReview ? '保存修改' : '发表评价' ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
<?php return ob_get_clean();
|
||||
};
|
||||
|
||||
/** 评论 / 回复表单(parent_id=0 是对评价发评论;>0 是回复某条评论) */
|
||||
$commentBox = static function (int $reviewId, int $parentId, string $summary) use ($m, $feelPick) {
|
||||
ob_start(); ?>
|
||||
<details class="rv-composer rv-inline">
|
||||
<summary class="rv-act-btn"><?= e($summary) ?></summary>
|
||||
<form method="post" action="<?= e(url('movies/comment_add')) ?>" class="rv-form rv-form-inline">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<input type="hidden" name="review_id" value="<?= $reviewId ?>">
|
||||
<input type="hidden" name="parent_id" value="<?= $parentId ?>">
|
||||
<textarea name="content" class="rv-textarea rv-textarea-sm" maxlength="<?= (int)\App\MovieReview::COMMENT_MAX ?>" required
|
||||
placeholder="<?= $parentId > 0 ? '回复这一条…' : '说说你的看法…' ?>"></textarea>
|
||||
<div class="rv-feeling-pick">
|
||||
<span class="rv-pick-label">观后感受(可多选)</span>
|
||||
<?= $feelPick() ?>
|
||||
</div>
|
||||
<div class="rv-form-foot">
|
||||
<button type="submit" class="btn btn-ghost btn-sm"><?= $parentId > 0 ? '发表回复' : '发表评论' ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
<?php return ob_get_clean();
|
||||
};
|
||||
?>
|
||||
<div class="page-head">
|
||||
<h1><?= e($m['title']) ?></h1>
|
||||
<span class="sub"><?= e((string)($m['starts_at'] ?? '日期待补')) ?></span>
|
||||
<span class="sub">观影记录</span>
|
||||
<div class="actions">
|
||||
<?php if ($canWrite): ?>
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('movies/edit', ['id' => (int)$m['id']])) ?>">编辑</a>
|
||||
@@ -30,80 +131,90 @@ $hasTicket = !empty($m['ticket_key']);
|
||||
<button type="submit" class="btn btn-danger btn-sm">删除</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('movies')) ?>">返回</a>
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('movies')) ?>">返回列表</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="movie-detail">
|
||||
<div class="m-poster-col">
|
||||
<!-- 主信息:英雄区 -->
|
||||
<div class="detail-hero">
|
||||
<div class="detail-poster<?= $hasPoster ? ' lb-openable' : '' ?>"
|
||||
<?php if ($hasPoster): ?>data-lb-index="0" role="button" tabindex="0" aria-label="放大查看海报" title="点击查看大图"<?php endif; ?>>
|
||||
<?php if ($hasPoster): ?>
|
||||
<a href="<?= e(media_url($m['poster_key'])) ?>" target="_blank" rel="noopener">
|
||||
<img class="m-poster-lg" src="<?= e(media_url($m['poster_key'])) ?>" alt="海报">
|
||||
</a>
|
||||
<img src="<?= e(media_url($m['poster_key'])) ?>" alt="海报">
|
||||
<?php else: ?>
|
||||
<span class="m-poster-lg m-poster-empty" aria-hidden="true">影</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($hasTicket): ?>
|
||||
<a href="<?= e(media_url($m['ticket_key'])) ?>" target="_blank" rel="noopener">
|
||||
<img class="m-ticket" src="<?= e(media_url($m['ticket_key'])) ?>" alt="票根" loading="lazy">
|
||||
</a>
|
||||
<span class="placeholder-poster">
|
||||
<span class="ph-icon" aria-hidden="true">影</span>
|
||||
<span class="placeholder-title"><?= e($m['title']) ?></span>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="card card-pad m-info">
|
||||
<?php if ($m['rating'] !== null || $feel): ?>
|
||||
<div class="m-score">
|
||||
<?php if ($m['rating'] !== null): ?>
|
||||
<span class="rating rating-lg"><?= e(rtrim(rtrim(number_format((float)$m['rating'], 1, '.', ''), '0'), '.')) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($feel as $f): ?>
|
||||
<span class="badge badge-feel"><?= e($f) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<dl class="kv">
|
||||
<div class="kv-row"><dt>观影日期</dt><dd><?= e((string)($m['starts_at'] ?? '日期待补')) ?></dd></div>
|
||||
<?php if (!empty($m['cinema_place']) || !empty($m['cinema_name']) || !empty($m['cinema_address'])): ?>
|
||||
<div class="kv-row"><dt>影院</dt><dd>
|
||||
<?= e(trim(implode(' ', array_filter([
|
||||
(string)($m['cinema_place'] ?? ''),
|
||||
(string)($m['cinema_name'] ?? ''),
|
||||
(string)($m['cinema_address'] ?? ''),
|
||||
], static fn($s) => $s !== '')))) ?>
|
||||
</dd></div>
|
||||
<div class="detail-info">
|
||||
<h1><?= e($m['title']) ?></h1>
|
||||
<div class="meta-row">
|
||||
<span>📅 <?= e($watchTxt) ?></span>
|
||||
<?php if (!empty($m['cinema_name'])): ?>
|
||||
<span style="color: <?= e((string)($m['cinema_color'] ?: '#888888')) ?>;">🏛️ <?= e($m['cinema_name']) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($m['location'])): ?>
|
||||
<div class="kv-row"><dt>地点</dt><dd><?= e($m['location']) ?></dd></div>
|
||||
<?php endif; ?>
|
||||
<div class="kv-row"><dt>同行的人</dt><dd>
|
||||
<?php if ($m['attendees']): ?>
|
||||
<?php foreach ($m['attendees'] as $i => $a): ?><?= $i > 0 ? '、' : '' ?><a href="<?= e(url('people/show', ['id' => (int)$a['id']])) ?>"><?= e($a['name']) ?></a><?php endforeach; ?>
|
||||
<?php else: ?>一个人看的<?php endif; ?>
|
||||
</dd></div>
|
||||
</dl>
|
||||
|
||||
<span>👥 观影人 <?= count($m['attendees']) ?> 人</span>
|
||||
</div>
|
||||
<?php if (!empty($m['info'])): ?>
|
||||
<div class="m-block"><h3>简介</h3><p><?= nl2br(e($m['info'])) ?></p></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($m['review'])): ?>
|
||||
<div class="m-block"><h3>观后感</h3><p><?= nl2br(e($m['review'])) ?></p></div>
|
||||
<div class="info-text"><?= nl2br(e($m['info'])) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($m['note'])): ?>
|
||||
<div class="m-block"><h3>备注</h3><p><?= nl2br(e($m['note'])) ?></p></div>
|
||||
<div class="remark-text">📝 <?= nl2br(e($m['note'])) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($m['photos']): ?>
|
||||
<section class="section">
|
||||
<h2 class="section-title">现场照片 <span class="hint"><?= count($m['photos']) ?> 张</span></h2>
|
||||
<div class="photo-grid">
|
||||
<?php foreach ($m['photos'] as $ph): ?>
|
||||
<div class="photo-cell">
|
||||
<a href="<?= e(media_url($ph['key'])) ?>" target="_blank" rel="noopener">
|
||||
<img src="<?= e(media_url($ph['key'])) ?>" alt="" loading="lazy">
|
||||
</a>
|
||||
<!-- 一起看的人 -->
|
||||
<div class="detail-section">
|
||||
<h3>一起看的人 <span class="badge"><?= count($m['attendees']) ?> 人</span></h3>
|
||||
<?php if ($m['attendees']): ?>
|
||||
<div class="viewers-list">
|
||||
<?php foreach ($m['attendees'] as $a): ?>
|
||||
<a class="viewer-tag" href="<?= e(url('people/show', ['id' => (int)$a['id']])) ?>">
|
||||
<?= $avatarHtml($a['avatar'], (string)$a['name']) ?>
|
||||
<?= e($a['name']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p class="empty-hint">一个人看的</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- 电影院信息 -->
|
||||
<div class="detail-section">
|
||||
<h3>电影院信息</h3>
|
||||
<?php if (!empty($m['cinema_name'])): ?>
|
||||
<div class="cinema-info">
|
||||
<span class="label">所在地方</span>
|
||||
<span class="value"><?= e((string)($m['cinema_place'] ?? '-')) ?></span>
|
||||
<span class="label">影院名称</span>
|
||||
<span class="value" style="color: <?= e((string)($m['cinema_color'] ?: '#888888')) ?>;"><?= e($m['cinema_name']) ?></span>
|
||||
<span class="label">详细地址</span>
|
||||
<span class="value"><?= e((string)($m['cinema_address'] ?? '-')) ?></span>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p class="empty-hint">未记录电影院信息</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- 影像记录(票根 + 现场照片,点开灯箱) -->
|
||||
<?php if ($hasTicket || $m['photos']): ?>
|
||||
<div class="detail-section">
|
||||
<h3>影像记录 <span class="badge"><?= count($m['photos']) + ($hasTicket ? 1 : 0) ?> 张</span></h3>
|
||||
<div class="gallery-grid">
|
||||
<?php if ($hasTicket): ?>
|
||||
<div class="gallery-item lb-openable" data-lb-index="1" role="button" tabindex="0" aria-label="放大查看票根" title="点击查看大图">
|
||||
<img src="<?= e(media_url($m['ticket_key'])) ?>" alt="票根" loading="lazy">
|
||||
<span class="label-badge">票根</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($m['photos'] as $i => $ph): ?>
|
||||
<div class="gallery-item lb-openable" data-lb-index="<?= $i + 2 ?>" role="button" tabindex="0" aria-label="放大查看照片" title="点击查看大图">
|
||||
<img src="<?= e(media_url((string)$ph['key'])) ?>" alt="" loading="lazy">
|
||||
<span class="label-badge">照片</span>
|
||||
<?php if ($canWrite): ?>
|
||||
<form method="post" action="<?= e(url('movies/photo_del')) ?>"
|
||||
onsubmit="return confirm('删除这张照片?')">
|
||||
@@ -116,12 +227,11 @@ $hasTicket = !empty($m['ticket_key']);
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canWrite): ?>
|
||||
<section class="section">
|
||||
<form method="post" enctype="multipart/form-data" action="<?= e(url('movies/photo_add')) ?>" class="card card-pad photo-add">
|
||||
<div class="detail-section">
|
||||
<form method="post" enctype="multipart/form-data" action="<?= e(url('movies/photo_add')) ?>" class="photo-add">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<label for="photo">补一张现场照片</label>
|
||||
@@ -130,5 +240,250 @@ $hasTicket = !empty($m['ticket_key']);
|
||||
<button type="submit" class="btn btn-ghost btn-sm">上传</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 评价与评论 -->
|
||||
<div class="detail-section" id="reviews">
|
||||
<h3>评价与评论
|
||||
<span class="badge">
|
||||
<?php if ($rvStats['count'] > 0): ?>
|
||||
<?= (int)$rvStats['count'] ?> 条评价 · 均分 <?= e($fmtNum($rvStats['avg'])) ?>
|
||||
<?php else: ?>
|
||||
还没有评价
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<?php if ($canWrite && !$myReview): ?>
|
||||
<?= $rvForm('写评价', $rvError !== '') ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$reviews): ?>
|
||||
<p class="empty-hint">还没有评价,等你来评一个。</p>
|
||||
<?php else: ?>
|
||||
<div class="rv-list">
|
||||
<?php foreach ($reviews as $rv): ?>
|
||||
<?php
|
||||
$rvMine = ((int)$rv['person_id'] === $meId);
|
||||
$rvName = (string)($rv['person_name'] ?? '已注销');
|
||||
$rvRate = (int)$rv['rating'];
|
||||
?>
|
||||
<div class="rv-item">
|
||||
<div class="rv-head">
|
||||
<?= $avatarHtml($rv['person_avatar'] ?? null, $rvName) ?>
|
||||
<span class="rv-who">
|
||||
<span class="rv-name"><?= e($rvName) ?></span>
|
||||
<?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>
|
||||
<?php if ($rvMine && $canWrite): ?>
|
||||
<form method="post" action="<?= e(url('movies/review_del')) ?>" class="rv-del"
|
||||
onsubmit="return confirm('删除我的这条评价?下面的评论也会一起删除,不可恢复。')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<input type="hidden" name="review_id" value="<?= (int)$rv['id'] ?>">
|
||||
<button type="submit" class="rv-del-btn" title="删除我的评价">×</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="rv-content"><?= nl2br(e($rv['content'])) ?></div>
|
||||
<?= $feelTags($rv['feelings']) ?>
|
||||
<?php if (!empty($rv['updated_at']) && $rv['updated_at'] !== $rv['created_at']): ?>
|
||||
<div class="rv-updated">已编辑 <?= e(substr((string)$rv['updated_at'], 0, 16)) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($rv['comments'])): ?>
|
||||
<div class="rv-comments">
|
||||
<?php foreach ($rv['comments'] as $thread): ?>
|
||||
<?php
|
||||
$c = $thread['comment'];
|
||||
$cMine = ((int)$c['person_id'] === $meId);
|
||||
$cName = (string)($c['person_name'] ?? '已注销');
|
||||
?>
|
||||
<div class="rv-thread">
|
||||
<div class="rv-comment">
|
||||
<?= $avatarHtml($c['person_avatar'] ?? null, $cName, 'rv-avatar-sm') ?>
|
||||
<span class="rv-c-body">
|
||||
<span class="rv-c-head">
|
||||
<span class="rv-name"><?= e($cName) ?></span>
|
||||
<?php if ($cMine): ?><span class="rv-mine">我</span><?php endif; ?>
|
||||
<span class="rv-time"><?= e(substr((string)$c['created_at'], 0, 16)) ?></span>
|
||||
<?php if ($cMine && $canWrite): ?>
|
||||
<form method="post" action="<?= e(url('movies/comment_del')) ?>" class="rv-del"
|
||||
onsubmit="return confirm('删除我的这条评论?它下面的回复也会一起删除。')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<input type="hidden" name="comment_id" value="<?= (int)$c['id'] ?>">
|
||||
<button type="submit" class="rv-del-btn" title="删除我的评论">×</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?= $feelTags($c['feelings']) ?>
|
||||
<span class="rv-c-text"><?= nl2br(e($c['content'])) ?></span>
|
||||
<span class="rv-c-acts"><?= $canWrite ? $commentBox((int)$rv['id'], (int)$c['id'], '回复') : '' ?></span>
|
||||
</span>
|
||||
</div>
|
||||
<?php foreach ($thread['replies'] as $rep): ?>
|
||||
<?php
|
||||
$rMine = ((int)$rep['person_id'] === $meId);
|
||||
$rName = (string)($rep['person_name'] ?? '已注销');
|
||||
?>
|
||||
<div class="rv-comment rv-comment-reply">
|
||||
<?= $avatarHtml($rep['person_avatar'] ?? null, $rName, 'rv-avatar-sm') ?>
|
||||
<span class="rv-c-body">
|
||||
<span class="rv-c-head">
|
||||
<span class="rv-name"><?= e($rName) ?></span>
|
||||
<?php if ($rMine): ?><span class="rv-mine">我</span><?php endif; ?>
|
||||
<span class="rv-time"><?= e(substr((string)$rep['created_at'], 0, 16)) ?></span>
|
||||
<?php if ($rMine && $canWrite): ?>
|
||||
<form method="post" action="<?= e(url('movies/comment_del')) ?>" class="rv-del"
|
||||
onsubmit="return confirm('删除我的这条回复?')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<input type="hidden" name="comment_id" value="<?= (int)$rep['id'] ?>">
|
||||
<button type="submit" class="rv-del-btn" title="删除我的回复">×</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php if (!empty($rep['reply_to_name'])): ?>
|
||||
<span class="rv-at">回复 <span>@<?= e($rep['reply_to_name']) ?></span></span>
|
||||
<?php endif; ?>
|
||||
<?= $feelTags($rep['feelings']) ?>
|
||||
<span class="rv-c-text"><?= nl2br(e($rep['content'])) ?></span>
|
||||
<span class="rv-c-acts"><?= $canWrite ? $commentBox((int)$rv['id'], (int)$rep['id'], '回复') : '' ?></span>
|
||||
</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canWrite): ?>
|
||||
<div class="rv-actions">
|
||||
<?= $commentBox((int)$rv['id'], 0, '评论') ?>
|
||||
<?php if ($rvMine): ?>
|
||||
<?= $rvForm('修改我的评价', $rvError !== '') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($hasPoster || $hasTicket || $m['photos']): ?>
|
||||
<!-- 图片灯箱:点任意一张图在当前页弹出,可左右翻看 / Esc 关闭(MovieLog 同款) -->
|
||||
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-hidden="true" aria-label="图片查看器">
|
||||
<button type="button" class="lb-btn lb-close" id="lbClose" aria-label="关闭">✕</button>
|
||||
<button type="button" class="lb-btn lb-nav lb-prev" id="lbPrev" aria-label="上一张">‹</button>
|
||||
<button type="button" class="lb-btn lb-nav lb-next" id="lbNext" aria-label="下一张">›</button>
|
||||
<div class="lb-stage" id="lbStage"><img class="lb-img" id="lbImg" alt=""></div>
|
||||
<div class="lb-bar">
|
||||
<span class="lb-caption" id="lbCaption"></span>
|
||||
<span class="lb-counter" id="lbCounter"></span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var items = [];
|
||||
<?php if ($hasPoster): ?>
|
||||
items.push({url: <?= json_encode(media_url((string)$m['poster_key']), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>, cap: <?= json_encode('海报 · ' . $m['title'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>});
|
||||
<?php endif; ?>
|
||||
<?php if ($hasTicket): ?>
|
||||
items.push({url: <?= json_encode(media_url((string)$m['ticket_key']), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>, cap: <?= json_encode('票根 · ' . $m['title'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>});
|
||||
<?php endif; ?>
|
||||
<?php foreach ($m['photos'] as $ph): ?>
|
||||
items.push({url: <?= json_encode(media_url((string)$ph['key']), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>, cap: <?= json_encode('照片 · ' . $m['title'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>});
|
||||
<?php endforeach; ?>
|
||||
var box = document.getElementById('lightbox');
|
||||
var stageEl = document.getElementById('lbStage');
|
||||
var imgEl = document.getElementById('lbImg');
|
||||
var capEl = document.getElementById('lbCaption');
|
||||
var cntEl = document.getElementById('lbCounter');
|
||||
var closeEl = document.getElementById('lbClose');
|
||||
var prevEl = document.getElementById('lbPrev');
|
||||
var nextEl = document.getElementById('lbNext');
|
||||
if (!box || !items.length) return;
|
||||
var idx = 0, opener = null;
|
||||
function render() {
|
||||
imgEl.src = items[idx].url;
|
||||
capEl.textContent = items[idx].cap;
|
||||
cntEl.textContent = (idx + 1) + ' / ' + items.length;
|
||||
var multi = items.length > 1;
|
||||
prevEl.hidden = !multi;
|
||||
nextEl.hidden = !multi;
|
||||
}
|
||||
function open(i, trigger) {
|
||||
idx = Math.max(0, Math.min(items.length - 1, i));
|
||||
opener = trigger || null;
|
||||
box.classList.add('open');
|
||||
box.setAttribute('aria-hidden', 'false');
|
||||
render();
|
||||
closeEl.focus();
|
||||
}
|
||||
function close() {
|
||||
box.classList.remove('open');
|
||||
box.setAttribute('aria-hidden', 'true');
|
||||
if (opener && opener.focus) opener.focus();
|
||||
opener = null;
|
||||
}
|
||||
function step(d) { idx = (idx + d + items.length) % items.length; render(); }
|
||||
Array.prototype.forEach.call(document.querySelectorAll('[data-lb-index]'), function (el) {
|
||||
var i = parseInt(el.getAttribute('data-lb-index'), 10);
|
||||
if (isNaN(i)) return;
|
||||
el.addEventListener('click', function (e) { e.preventDefault(); open(i, el); });
|
||||
el.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(i, el); }
|
||||
});
|
||||
});
|
||||
closeEl.addEventListener('click', close);
|
||||
prevEl.addEventListener('click', function (e) { e.stopPropagation(); step(-1); });
|
||||
nextEl.addEventListener('click', function (e) { e.stopPropagation(); step(1); });
|
||||
box.addEventListener('click', function (e) { if (e.target === box || e.target === stageEl) close(); });
|
||||
imgEl.addEventListener('click', function (e) { e.stopPropagation(); });
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (!box.classList.contains('open')) return;
|
||||
if (e.key === 'Escape') { close(); }
|
||||
else if (e.key === 'ArrowLeft') { e.preventDefault(); step(-1); }
|
||||
else if (e.key === 'ArrowRight') { e.preventDefault(); step(1); }
|
||||
});
|
||||
var tx = 0, ty = 0;
|
||||
stageEl.addEventListener('touchstart', function (e) {
|
||||
tx = e.changedTouches[0].clientX;
|
||||
ty = e.changedTouches[0].clientY;
|
||||
}, { passive: true });
|
||||
stageEl.addEventListener('touchend', function (e) {
|
||||
var dx = e.changedTouches[0].clientX - tx;
|
||||
var dy = e.changedTouches[0].clientY - ty;
|
||||
if (Math.abs(dx) > 40 && Math.abs(dx) > Math.abs(dy)) step(dx < 0 ? 1 : -1);
|
||||
}, { passive: true });
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canWrite): ?>
|
||||
<!-- 打分实时提示「几分 · 怎么样」;纯增强,radio 没 JS 也能选 -->
|
||||
<script>
|
||||
(function () {
|
||||
var form = document.getElementById('reviewForm');
|
||||
var word = document.getElementById('rvWord');
|
||||
if (!form || !word) return;
|
||||
var WORDS = <?= json_encode(\App\MovieReview::ratingWords(), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||
function sync() {
|
||||
var picked = form.querySelector('input[name="rating"]:checked');
|
||||
if (!picked) { word.textContent = '未评分'; word.classList.remove('is-set'); return; }
|
||||
var v = parseInt(picked.value, 10);
|
||||
word.textContent = v + ' 分 · ' + (WORDS[v - 1] || '');
|
||||
word.classList.add('is-set');
|
||||
}
|
||||
Array.prototype.forEach.call(form.querySelectorAll('input[name="rating"]'), function (el) {
|
||||
el.addEventListener('change', sync);
|
||||
});
|
||||
sync();
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in New Issue
Block a user