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:
@@ -13,11 +13,13 @@ require __DIR__ . '/app/bootstrap.php';
|
||||
use App\Account;
|
||||
use App\Auth;
|
||||
use App\Birthday;
|
||||
use App\Cinema;
|
||||
use App\Crypto;
|
||||
use App\Database;
|
||||
use App\Family;
|
||||
use App\Kinship;
|
||||
use App\Movie;
|
||||
use App\MovieReview;
|
||||
use App\Nickname;
|
||||
use App\Person;
|
||||
use App\PersonGroup;
|
||||
@@ -1337,21 +1339,16 @@ switch ($r) {
|
||||
// 见 App\Movie。可见性与名册同一口径:能登录就能看,能写才能改;
|
||||
// 迁进来的历史记录 source_ref = 'movielog:movie:<id>'(重跑迁移的幂等锚点)。
|
||||
|
||||
/** 观影时间线 —— 按年分组倒序。 */
|
||||
/** 观影瀑布流(v9,MovieLog 同款海报墙;带关键词搜索)。 */
|
||||
case 'movies':
|
||||
$familyId = (int)Auth::familyId();
|
||||
$rows = Movie::all($familyId);
|
||||
$years = [];
|
||||
foreach ($rows as $r) {
|
||||
$y = $r['starts_at'] !== null ? (int)substr((string)$r['starts_at'], 0, 4) : 0;
|
||||
$years[$y][] = $r;
|
||||
}
|
||||
unset($r);
|
||||
krsort($years);
|
||||
$keyword = trim((string)($_GET['keyword'] ?? ''));
|
||||
$rows = Movie::all($familyId, $keyword);
|
||||
View::render('movies/index', [
|
||||
'title' => '观影记录',
|
||||
'years' => $years,
|
||||
'total' => count($rows),
|
||||
'title' => '观影记录',
|
||||
'rows' => $rows,
|
||||
'total' => count($rows),
|
||||
'keyword' => $keyword,
|
||||
], 'movies');
|
||||
break;
|
||||
|
||||
@@ -1362,19 +1359,28 @@ switch ($r) {
|
||||
flash('err', '找不到这条观影记录。');
|
||||
redirect('movies');
|
||||
}
|
||||
$meId = (int)Auth::personId();
|
||||
$rvError = '';
|
||||
View::render('movies/show', [
|
||||
'title' => (string)$m['title'],
|
||||
'm' => $m,
|
||||
'title' => (string)$m['title'],
|
||||
'm' => $m,
|
||||
'reviews' => MovieReview::forEvent((int)$m['id']),
|
||||
'rvStats' => MovieReview::stats((int)$m['id']),
|
||||
'myReview' => MovieReview::mine((int)$m['id'], $meId),
|
||||
'meId' => $meId,
|
||||
'rvError' => $rvError,
|
||||
'feelCat' => MovieReview::CATALOG,
|
||||
], 'movies');
|
||||
break;
|
||||
|
||||
case 'movies/new':
|
||||
Auth::requireWrite();
|
||||
View::render('movies/form', [
|
||||
'title' => '记一场电影',
|
||||
'm' => null,
|
||||
'people' => Person::roster((int)Auth::familyId(), (int)Auth::personId(), 'member', ''),
|
||||
'errors' => [],
|
||||
'title' => '记一场电影',
|
||||
'm' => null,
|
||||
'people' => Person::roster((int)Auth::familyId(), (int)Auth::personId(), 'member', ''),
|
||||
'cinemas' => Cinema::all((int)Auth::familyId()),
|
||||
'errors' => [],
|
||||
], 'movies');
|
||||
break;
|
||||
|
||||
@@ -1387,10 +1393,11 @@ switch ($r) {
|
||||
redirect('movies');
|
||||
}
|
||||
View::render('movies/form', [
|
||||
'title' => '编辑观影记录',
|
||||
'm' => $m,
|
||||
'people' => Person::roster($familyId, (int)Auth::personId(), 'member', ''),
|
||||
'errors' => [],
|
||||
'title' => '编辑观影记录',
|
||||
'm' => $m,
|
||||
'people' => Person::roster($familyId, (int)Auth::personId(), 'member', ''),
|
||||
'cinemas' => Cinema::all($familyId),
|
||||
'errors' => [],
|
||||
], 'movies');
|
||||
break;
|
||||
|
||||
@@ -1410,18 +1417,22 @@ switch ($r) {
|
||||
redirect('movies');
|
||||
}
|
||||
|
||||
// 起止时刻精确到秒(_datetime.php 控件 → read_dt_input)
|
||||
$startsAt = read_dt_input('starts_at');
|
||||
$endsAt = read_dt_input('ends_at');
|
||||
$cinemaId = (int)($_POST['cinema_id'] ?? 0);
|
||||
if ($cinemaId > 0 && !Cinema::find($familyId, $cinemaId)) {
|
||||
$cinemaId = 0; // 表单可改:只认本家的影院
|
||||
}
|
||||
|
||||
$d = [
|
||||
'title' => trim((string)($_POST['title'] ?? '')),
|
||||
'starts_at' => trim((string)($_POST['starts_at'] ?? '')),
|
||||
'starts_at' => (string)($startsAt ?? ''),
|
||||
'ends_at' => (string)($endsAt ?? ''),
|
||||
'location' => trim((string)($_POST['location'] ?? '')),
|
||||
'note' => trim((string)($_POST['note'] ?? '')),
|
||||
'info' => trim((string)($_POST['info'] ?? '')),
|
||||
'rating' => trim((string)($_POST['rating'] ?? '')),
|
||||
'review' => trim((string)($_POST['review'] ?? '')),
|
||||
'feelings' => array_values((array)($_POST['feelings'] ?? [])),
|
||||
'cinema_place' => trim((string)($_POST['cinema_place'] ?? '')),
|
||||
'cinema_name' => trim((string)($_POST['cinema_name'] ?? '')),
|
||||
'cinema_address' => trim((string)($_POST['cinema_address'] ?? '')),
|
||||
'cinema_id' => $cinemaId,
|
||||
// 同行的人:只认「确实存在且属于这个家」的 id —— 表单可以改,
|
||||
// 不能凭它把别人家的成员挂进自己家的观影记录里(会顺着详情页漏人名)。
|
||||
'attendees' => array_map('intval', (array)($_POST['attendees'] ?? [])),
|
||||
@@ -1429,7 +1440,13 @@ switch ($r) {
|
||||
$_familyPeople = Person::map($familyId);
|
||||
$d['attendees'] = array_values(array_filter($d['attendees'],
|
||||
static fn($pid) => isset($_familyPeople[$pid])));
|
||||
$errors = $d['title'] === '' ? ['title' => '片名不能为空'] : [];
|
||||
$errors = [];
|
||||
if ($d['title'] === '') {
|
||||
$errors['title'] = '片名不能为空';
|
||||
} elseif ($d['starts_at'] !== '' && $d['ends_at'] !== ''
|
||||
&& strcmp($d['ends_at'], $d['starts_at']) < 0) {
|
||||
$errors['ends_at'] = '散场时间不能早于开场时间';
|
||||
}
|
||||
|
||||
// ⚠️ 图片上传失败**不挡保存**(与头像同一思路:一条记录因为一张 5MB 原图
|
||||
// 存不进去,看起来像功能坏了)。现在是先存记录,图片的事单独提示。
|
||||
@@ -1479,10 +1496,11 @@ switch ($r) {
|
||||
if ($errors) {
|
||||
set_old($_POST);
|
||||
View::render('movies/form', [
|
||||
'title' => $isEdit ? '编辑观影记录' : '记一场电影',
|
||||
'm' => $m,
|
||||
'people' => Person::roster($familyId, $me, 'member', ''),
|
||||
'errors' => $errors,
|
||||
'title' => $isEdit ? '编辑观影记录' : '记一场电影',
|
||||
'm' => $m,
|
||||
'people' => Person::roster($familyId, $me, 'member', ''),
|
||||
'cinemas' => Cinema::all($familyId),
|
||||
'errors' => $errors,
|
||||
], 'movies');
|
||||
exit;
|
||||
}
|
||||
@@ -1552,6 +1570,149 @@ switch ($r) {
|
||||
redirect('movies');
|
||||
break;
|
||||
|
||||
// ---------------- 影院管理(v9,MovieLog 同款) ----------------
|
||||
|
||||
/** 影院管理单页:上面加/编表单,下面列表。GET 带参数时进入编辑态。 */
|
||||
case 'movies/cinemas':
|
||||
$familyId = (int)Auth::familyId();
|
||||
$editId = (int)($_GET['edit'] ?? 0);
|
||||
$editing = $editId > 0 ? Cinema::find($familyId, $editId) : null;
|
||||
View::render('movies/cinemas', [
|
||||
'title' => '影院管理',
|
||||
'cinemas' => Cinema::all($familyId),
|
||||
'editing' => $editing,
|
||||
], 'cinemas');
|
||||
break;
|
||||
|
||||
case 'movies/cinemas_save':
|
||||
$familyId = (int)Auth::familyId();
|
||||
Auth::requireWrite();
|
||||
csrf_check();
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$editing = $id > 0 ? Cinema::find($familyId, $id) : null;
|
||||
if ($id > 0 && !$editing) {
|
||||
flash('err', '找不到这家影院。');
|
||||
redirect('movies/cinemas');
|
||||
}
|
||||
$err = Cinema::save(
|
||||
$familyId,
|
||||
$id,
|
||||
trim((string)($_POST['place'] ?? '')),
|
||||
trim((string)($_POST['name'] ?? '')),
|
||||
trim((string)($_POST['address'] ?? '')) !== '' ? trim((string)($_POST['address'] ?? '')) : null,
|
||||
Cinema::normalizeColor((string)($_POST['color'] ?? '#888888'))
|
||||
);
|
||||
if ($err !== '') {
|
||||
flash('err', $err);
|
||||
redirect('movies/cinemas', $id > 0 ? ['edit' => $id] : []);
|
||||
}
|
||||
flash('ok', $id > 0 ? '影院信息已更新。' : '影院已添加。');
|
||||
redirect('movies/cinemas');
|
||||
break;
|
||||
|
||||
case 'movies/cinemas_del':
|
||||
$familyId = (int)Auth::familyId();
|
||||
Auth::requireWrite();
|
||||
csrf_check();
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if (Cinema::find($familyId, $id)) {
|
||||
Cinema::destroy($familyId, $id);
|
||||
flash('ok', '影院已删除,关联观影记录的影院信息已置空。');
|
||||
} else {
|
||||
flash('err', '找不到这家影院。');
|
||||
}
|
||||
redirect('movies/cinemas');
|
||||
break;
|
||||
|
||||
// ---------------- 评价与讨论(v9,MovieLog 同款) ----------------
|
||||
// 评价人是「登录账号背后的人」,与同行观影人无关;访客只读(requireWrite)。
|
||||
|
||||
case 'movies/review_save':
|
||||
$familyId = (int)Auth::familyId();
|
||||
$meId = (int)Auth::personId();
|
||||
Auth::requireWrite();
|
||||
csrf_check();
|
||||
$eventId = (int)($_POST['id'] ?? 0);
|
||||
$m = Movie::find($familyId, $eventId);
|
||||
if (!$m) {
|
||||
flash('err', '找不到这条观影记录。');
|
||||
redirect('movies');
|
||||
}
|
||||
$my = MovieReview::mine($eventId, $meId);
|
||||
$err = MovieReview::save(
|
||||
$eventId, $familyId, $meId,
|
||||
(int)($_POST['rating'] ?? 0),
|
||||
trim((string)($_POST['content'] ?? '')),
|
||||
array_values((array)($_POST['feelings'] ?? [])),
|
||||
$my !== null
|
||||
);
|
||||
if ($err !== '') {
|
||||
flash('err', $err);
|
||||
} else {
|
||||
flash('ok', $my !== null ? '评价已更新。' : '评价已发布。');
|
||||
}
|
||||
redirect('movies/show', ['id' => $eventId], 'reviews');
|
||||
break;
|
||||
|
||||
case 'movies/review_del':
|
||||
$familyId = (int)Auth::familyId();
|
||||
Auth::requireWrite();
|
||||
csrf_check();
|
||||
$eventId = (int)($_POST['id'] ?? 0);
|
||||
if (!Movie::find($familyId, $eventId)) {
|
||||
flash('err', '找不到这条观影记录。');
|
||||
redirect('movies');
|
||||
}
|
||||
if (!MovieReview::deleteMine((int)($_POST['review_id'] ?? 0), (int)Auth::personId())) {
|
||||
flash('err', '只能删除自己的评价。');
|
||||
} else {
|
||||
flash('ok', '评价已删除。');
|
||||
}
|
||||
redirect('movies/show', ['id' => $eventId], 'reviews');
|
||||
break;
|
||||
|
||||
case 'movies/comment_add':
|
||||
$familyId = (int)Auth::familyId();
|
||||
Auth::requireWrite();
|
||||
csrf_check();
|
||||
$eventId = (int)($_POST['id'] ?? 0);
|
||||
$reviewId = (int)($_POST['review_id'] ?? 0);
|
||||
$parentId = (int)($_POST['parent_id'] ?? 0);
|
||||
if (!Movie::find($familyId, $eventId)) {
|
||||
flash('err', '找不到这条观影记录。');
|
||||
redirect('movies');
|
||||
}
|
||||
$res = MovieReview::addComment(
|
||||
$reviewId, (int)Auth::personId(),
|
||||
trim((string)($_POST['content'] ?? '')),
|
||||
array_values((array)($_POST['feelings'] ?? [])),
|
||||
$parentId
|
||||
);
|
||||
if (is_string($res)) {
|
||||
flash('err', $res);
|
||||
} else {
|
||||
flash('ok', $parentId > 0 ? '回复已发表。' : '评论已发表。');
|
||||
}
|
||||
redirect('movies/show', ['id' => $eventId], 'reviews');
|
||||
break;
|
||||
|
||||
case 'movies/comment_del':
|
||||
$familyId = (int)Auth::familyId();
|
||||
Auth::requireWrite();
|
||||
csrf_check();
|
||||
$eventId = (int)($_POST['id'] ?? 0);
|
||||
if (!Movie::find($familyId, $eventId)) {
|
||||
flash('err', '找不到这条观影记录。');
|
||||
redirect('movies');
|
||||
}
|
||||
if (!MovieReview::deleteComment((int)($_POST['comment_id'] ?? 0), (int)Auth::personId())) {
|
||||
flash('err', '只能删除自己的评论。');
|
||||
} else {
|
||||
flash('ok', '评论已删除。');
|
||||
}
|
||||
redirect('movies/show', ['id' => $eventId], 'reviews');
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(404);
|
||||
View::render('error', [
|
||||
|
||||
Reference in New Issue
Block a user