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:
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* 观影表单(新增 / 编辑,一张表单两用 —— 与 people/form 同一思路)。
|
||||
*
|
||||
* 字段分三块:看什么(片名/日期/评分/感受)、跟谁(同行的人)、在哪看(影院三段),
|
||||
* 再加自由文本(简介/观后感/备注)。海报与票根是 free 槽位(v8):
|
||||
* 不进裁剪器、整图等比缩进框内,所以这里不放裁剪框,只放普通文件控件。
|
||||
*
|
||||
* 现场照片只在**新增**时提供多选;编辑时在详情页里逐张补/删 ——
|
||||
* 一次改一处的表单,比一个巨型表单不容易出错。
|
||||
*/
|
||||
$isEdit = !empty($m['id']);
|
||||
|
||||
// 回填:校验失败重画时用用户刚提交的值(old()),否则用库里的值
|
||||
$curAttendees = array_map('intval', array_column($m['attendees'] ?? [], 'id'));
|
||||
$curFeelings = [];
|
||||
if (!empty($m['feelings'])) {
|
||||
$curFeelings = json_decode((string)$m['feelings'], true) ?: [];
|
||||
}
|
||||
$selAttendees = array_map('intval', (array)old('attendees', $curAttendees));
|
||||
$selFeelings = (array)old('feelings', $curFeelings);
|
||||
$rtVal = old('rating', $m['rating'] ?? '');
|
||||
?>
|
||||
<div class="page-head">
|
||||
<h1><?= $isEdit ? '编辑观影记录' : '记一场电影' ?></h1>
|
||||
<span class="sub"><?= $isEdit ? e($m['title']) : '片名必填,其他都可以以后再补' ?></span>
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
action="<?= e(url($isEdit ? 'movies/update' : 'movies/create')) ?>">
|
||||
<?= csrf_field() ?>
|
||||
<?php if ($isEdit): ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card card-pad">
|
||||
<div class="form-grid">
|
||||
<div class="field full">
|
||||
<label for="title">片名 *</label>
|
||||
<input class="input" id="title" name="title" value="<?= e(old('title', $m['title'] ?? '')) ?>" required>
|
||||
<?php if (!empty($errors['title'])): ?><span class="err"><?= e($errors['title']) ?></span><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="starts_at">观影日期</label>
|
||||
<input class="input" id="starts_at" name="starts_at" type="date"
|
||||
value="<?= e(old('starts_at', $m['starts_at'] ?? '')) ?>">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="rating">评分(1-10)</label>
|
||||
<select class="select" id="rating" name="rating">
|
||||
<option value="">不评</option>
|
||||
<?php for ($i = 10; $i >= 1; $i--): ?>
|
||||
<option value="<?= $i ?>" <?= (string)$rtVal === (string)$i ? 'selected' : '' ?>><?= $i ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label>观后感受</label>
|
||||
<div class="feel-grid">
|
||||
<?php foreach (\App\Movie::FEELINGS as $f): ?>
|
||||
<label class="feel-item">
|
||||
<input type="checkbox" name="feelings[]" value="<?= e($f) ?>"
|
||||
<?= in_array($f, $selFeelings, true) ? 'checked' : '' ?>>
|
||||
<span><?= e($f) ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<span class="tip">MovieLog 里记的英文标签(warm / excited…)搬过来时已经翻成中文。</span>
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label>同行的人</label>
|
||||
<?php if ($people): ?>
|
||||
<div class="feel-grid">
|
||||
<?php foreach ($people as $p): ?>
|
||||
<label class="feel-item">
|
||||
<input type="checkbox" name="attendees[]" value="<?= (int)$p['id'] ?>"
|
||||
<?= in_array((int)$p['id'], $selAttendees, true) ? 'checked' : '' ?>>
|
||||
<span><?= e($p['shown'] ?? $p['name']) ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<span class="tip">名册里还没有人,先把家人加进名册再来记。</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="cinema_place">影院 · 城市/商圈</label>
|
||||
<input class="input" id="cinema_place" name="cinema_place"
|
||||
value="<?= e(old('cinema_place', $m['cinema_place'] ?? '')) ?>" placeholder="如:青岛 · 崂山区">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="cinema_name">影院 · 名字</label>
|
||||
<input class="input" id="cinema_name" name="cinema_name"
|
||||
value="<?= e(old('cinema_name', $m['cinema_name'] ?? '')) ?>" placeholder="如:万达影城 CBD 店">
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label for="cinema_address">影院 · 地址</label>
|
||||
<input class="input" id="cinema_address" name="cinema_address"
|
||||
value="<?= e(old('cinema_address', $m['cinema_address'] ?? '')) ?>">
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label for="info">简介</label>
|
||||
<textarea class="input" id="info" name="info" rows="3"
|
||||
placeholder="剧情简介(可选)"><?= e(old('info', $m['info'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label for="review">观后感</label>
|
||||
<textarea class="input" id="review" name="review" rows="4"
|
||||
placeholder="看完想记下来的话"><?= e(old('review', $m['review'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label for="note">备注</label>
|
||||
<textarea class="input" id="note" name="note" rows="2"><?= e(old('note', $m['note'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="poster">海报</label>
|
||||
<input class="input" id="poster" name="poster" type="file" accept="image/*">
|
||||
<?php if ($isEdit && !empty($m['poster_key'])): ?>
|
||||
<span class="tip">已有海报,不选新图就保留原图。</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="ticket">票根</label>
|
||||
<input class="input" id="ticket" name="ticket" type="file" accept="image/*">
|
||||
<?php if ($isEdit && !empty($m['ticket_key'])): ?>
|
||||
<span class="tip">已有票根,不选新图就保留原图。</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!$isEdit): ?>
|
||||
<div class="field full">
|
||||
<label for="photo">现场照片(可多选)</label>
|
||||
<input class="input" id="photo" name="photo[]" type="file" accept="image/*" multiple>
|
||||
<span class="tip">之后也可以在详情页里逐张补传。</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<button type="submit" class="btn"><?= $isEdit ? '保存' : '记下这场电影' ?></button>
|
||||
<a class="btn btn-ghost"
|
||||
href="<?= e($isEdit ? url('movies/show', ['id' => (int)$m['id']]) : url('movies')) ?>">取消</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -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; ?>
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* 观影详情页。
|
||||
*
|
||||
* 海报 / 票根 / 照片一律走 media_url() 代理(全站唯一出口,见 helpers.php);
|
||||
* 感受标签是迁移时已翻成中文的 JSON 数组,这里只展示不加工。
|
||||
* 访客(只读)看不到编辑/删除/传照片那些表单 —— 与名册同一口径。
|
||||
*/
|
||||
$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']);
|
||||
?>
|
||||
<div class="page-head">
|
||||
<h1><?= e($m['title']) ?></h1>
|
||||
<span class="sub"><?= e((string)($m['starts_at'] ?? '日期待补')) ?></span>
|
||||
<div class="actions">
|
||||
<?php if ($canWrite): ?>
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('movies/edit', ['id' => (int)$m['id']])) ?>">编辑</a>
|
||||
<form method="post" action="<?= e(url('movies/destroy')) ?>"
|
||||
onsubmit="return confirm('删除这条观影记录?海报、票根、照片也会一并删除,不可恢复。')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="movie-detail">
|
||||
<div class="m-poster-col">
|
||||
<?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>
|
||||
<?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>
|
||||
<?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>
|
||||
<?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>
|
||||
|
||||
<?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>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($m['note'])): ?>
|
||||
<div class="m-block"><h3>备注</h3><p><?= nl2br(e($m['note'])) ?></p></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>
|
||||
<?php if ($canWrite): ?>
|
||||
<form method="post" action="<?= e(url('movies/photo_del')) ?>"
|
||||
onsubmit="return confirm('删除这张照片?')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<input type="hidden" name="photo_id" value="<?= (int)$ph['id'] ?>">
|
||||
<button type="submit" class="photo-del" title="删除这张照片">×</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?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">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<label for="photo">补一张现场照片</label>
|
||||
<div class="photo-add-row">
|
||||
<input class="input" id="photo" name="photo" type="file" accept="image/*" required>
|
||||
<button type="submit" class="btn btn-ghost btn-sm">上传</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
Reference in New Issue
Block a user