v8:观影落地(MovieLog 整体迁入统一事件原语)+ 三处 UI 撤除

- Schema v8:events / movie_details / event_attendees / event_photos 四张表,
  uk_event_source(family_id, source_ref) 作迁移幂等锚
- Movie 模型:时间线/详情/增删改、同行人整组替换、照片逐张管理、感受英→中映射
- ImageSlot 新增 poster(2:3) / ticket(free) / photo(free) 三槽位;
  free 槽位不裁剪、整图等比缩进框(只缩不放大)
- OssDriver::copyObject:同桶 CopyObject(手写 V1 签名,x-oss-* 头按字典序进签名串)
- 路由:movies 时间线/详情/新增/编辑/删除/补照片/删照片;同行人按家庭过滤;
  访客只读(requireWrite)
- UI 撤除:首页「我的一句话」、家谱页「补充我的亲属关系 / 看整个名册」两个按钮;
  顶栏加「观影」入口
- 修潜伏 bug:ImageSlot::parseRatio 的正则用 / 作分隔符,字符类里的 / 把模式
  提前截断(Unknown modifier ']')—— ratio() 只在上传 POST 里被调到,一直没被观察到
- 线上数据迁移(临时 token 门禁页,用完即删):MovieLog 219 条观影 → events,
  14 位观影人新增进名册(带 MovieLog 头像),431 个 OSS 对象同桶拷贝
  (movielog/uploads/<dir>/x → family/<dir>/x),review/feelings/影院三段/照片全量搬
This commit is contained in:
2026-09-30 20:22:46 +08:00
parent 809443d429
commit ff353b1eee
13 changed files with 1112 additions and 43 deletions
+61
View File
@@ -0,0 +1,61 @@
<?php
/**
* 观影时间线 —— 按年分组倒序(v8,MovieLog 迁入后的新家)。
*
* 没填日期的记录落在键 0,沉到最底下单独一组「日期待补」,
* 不让它混进任何一个年份里(跟名册「未分类」栏是同一个思路)。
*/
$canWrite = \App\Auth::canWrite();
?>
<div class="page-head">
<h1>观影记录</h1>
<span class="sub">一共 <?= (int)$total ?> 场</span>
<div class="actions">
<?php if ($canWrite): ?>
<a class="btn" href="<?= e(url('movies/new')) ?>">记一场电影</a>
<?php endif; ?>
</div>
</div>
<?php if ($total === 0): ?>
<div class="card card-pad empty">
<b>还没有观影记录</b>
一家人一起看过的电影,都会按年份排在这里。
</div>
<?php else: ?>
<?php foreach ($years as $y => $list): ?>
<section class="section">
<h2 class="section-title"><?= $y > 0 ? (int)$y : '日期待补' ?> <span class="hint"><?= count($list) ?> 场</span></h2>
<div class="movie-list">
<?php foreach ($list as $mv): ?>
<?php
// 评分统一一位小数内显示:9 → "9",8.5 → "8.5"(库里是 0-10 整数,这里只做展示兜底)
$rtTxt = $mv['rating'] !== null
? rtrim(rtrim(number_format((float)$mv['rating'], 1, '.', ''), '0'), '.')
: '';
?>
<a class="movie-card" href="<?= e(url('movies/show', ['id' => (int)$mv['id']])) ?>">
<?php if (!empty($mv['poster_key'])): ?>
<img class="m-poster" src="<?= e(media_url($mv['poster_key'])) ?>" alt="" loading="lazy">
<?php else: ?>
<span class="m-poster m-poster-empty" aria-hidden="true">影</span>
<?php endif; ?>
<span class="m-main">
<span class="m-title">
<?= e($mv['title']) ?>
<?php if ($rtTxt !== ''): ?><span class="rating"><?= e($rtTxt) ?></span><?php endif; ?>
</span>
<span class="m-meta">
<?= e((string)($mv['starts_at'] ?? '日期待补')) ?>
<?php if (!empty($mv['cinema_name'])): ?> · <?= e($mv['cinema_name']) ?><?php endif; ?>
</span>
<?php if (!empty($mv['attendee_names'])): ?>
<span class="m-who">和 <?= e($mv['attendee_names']) ?></span>
<?php endif; ?>
</span>
</a>
<?php endforeach; ?>
</div>
</section>
<?php endforeach; ?>
<?php endif; ?>