diff --git a/app/MovieStats.php b/app/MovieStats.php index 346874b..461dd45 100644 --- a/app/MovieStats.php +++ b/app/MovieStats.php @@ -72,7 +72,56 @@ final class MovieStats return $st->fetchAll(); } - /** 按观影人:参与了多少场(带头像,供统计页画条;名称按「我叫他什么」显示) */ + /** + * 月份 / 星期的**图表配色**(v40)。 + * + * 🔴 用户 2026-10-10:「观影统计里面,月份分布和星期分布,12 个月用 12 种不同的颜色, + * 星期 7 天用 7 天用 7 种不同的颜色。」 + * + * ⚠️ 放在这里(而不是模板里各写一份)的理由:月份分布与星期分布分别出现在 + * `report.php`(年度报告)与 `stats.php`(月度趋势)两处,颜色必须是**同一套**, + * 否则同一个月在两个页面是两种颜色 —— 那种不一致用户一眼就会看出来。 + * ⚠️ 色板是**环绕色相**取的 12 档(红→橙→黄→绿→青→蓝→靛→紫→品红), + * 相邻两档明度接近、色相拉开,在宣纸底上都能看清; + * 没有用"彩虹高饱和"是因为那和大面积暖棕的品牌底色打架。 + */ + public const MONTH_COLORS = [ + 1 => '#C0533F', '#D2703C', '#DFA13A', '#C7B23E', + '#8CB04A', '#5FA05C', '#3F9E85', '#3E8FA6', + '#4478B5', '#5B63B0', '#8455AC', '#B0548F', + ]; + + /** 星期配色:日 → 六(7 档,与月份色板取同一条色相环上均匀间隔的 7 个点) */ + public const WEEKDAY_COLORS = [ + '日' => '#C0533F', '一' => '#D98A3C', '二' => '#C7B23E', + '三' => '#5FA05C', '四' => '#3E8FA6', '五' => '#4478B5', + '六' => '#8455AC', + ]; + + /** 取某个月(1-12)的颜色;越界回退到品牌色,绝不返回空串(那会画成黑块)。 */ + public static function monthColor(int $m): string + { + return self::MONTH_COLORS[$m] ?? '#A84A28'; + } + + /** 取某个星期('日'..'六')的颜色 */ + public static function weekdayColor(string $w): string + { + return self::WEEKDAY_COLORS[$w] ?? '#A84A28'; + } + + /** 按观影人:参与了多少场(带头像,供统计页画条;名称按「我叫他什么」显示) + * + * 🔴 v40(用户 2026-10-10):「观影统计里面,如果这个人在名册里面,但是他一部电影的 + * 记录也没有,就不要在下面的这个按观影人统计里面出现了,太长了。」 + * ⇒ **只列出真的参与过至少一场的人**(`HAVING cnt > 0`)。 + * + * ⚠️ 为什么原来是 LEFT JOIN 全列:那是"按人统计"字面意义上的做法,也方便看出 + * "谁一场都没看过"。但用户的实际痛点是**列表太长**(名册上千人时, + * 这个区块会被几百行"0 场"淹没,真正有数据的人反而找不到)。 + * ⇒ 按用户要求过滤;"0 场"这件事本来也不是这个页面要回答的问题。 + * ⚠️ `HAVING` 而不是 `WHERE cnt > 0`:`cnt` 是聚合结果,`WHERE` 阶段还不存在。 + * 写成 `WHERE` 会被 MySQL 报 `Unknown column 'cnt'`。 */ public static function byPerson(int $familyId, int $viewerId = 0): array { $st = Database::get()->prepare( @@ -83,6 +132,7 @@ final class MovieStats LEFT JOIN nicknames n ON n.target_id = p.id AND n.viewer_id = ? WHERE p.family_id = ? AND p.kind = 'member' GROUP BY p.id, p.name, p.avatar, n.nickname + HAVING cnt > 0 ORDER BY cnt DESC, p.id" ); $st->execute([$familyId, $viewerId, $familyId]); diff --git a/app/View.php b/app/View.php index 090c373..afa6620 100644 --- a/app/View.php +++ b/app/View.php @@ -28,6 +28,31 @@ final class View require dirname(__DIR__) . '/templates/layout.php'; } + /** + * 只渲染**模板本身**,返回字符串(不套 layout)。 + * + * 🔴 v40(用户 2026-10-10):「观影详情里面的编辑按钮应该也是弹出的,和新增是一样的, + * 编辑完以后,页面动态更新。」 + * ⇒ AJAX 保存**校验失败**时,必须把这同一个表单**原样重画**回去 + * (字段报错、`old()` 回填、附件/海报面板全靠模板自己产出)。 + * 如果不重画而只回一个错误文案,用户就会看到"值全丢了、红字也没有"—— + * 与整页路径(`View::render`)行为不一致,那种"两条路各说各话"的 bug 最难查。 + * + * ⚠️ 与 `render()` 的唯一区别:不 require layout.php。 + * layout 会输出 ``/顶栏,塞进一段 innerHTML 里会直接坏掉结构。 + */ + public static function renderPartial(string $tpl, array $data = []): string + { + $file = dirname(__DIR__) . '/templates/' . $tpl . '.php'; + if (!is_file($file)) { + throw new \RuntimeException('模板不存在:' . $tpl); + } + extract($data, EXTR_SKIP); + ob_start(); + require $file; + return (string)ob_get_clean(); + } + /** * 独立的整页(登录、安装、错误),不套顶栏 —— 但**仍然要是一份完整 HTML**。 * diff --git a/assets/css/app.css b/assets/css/app.css index 486f600..f96ddcb 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -15,6 +15,11 @@ --brand-ink:#6E3820; --green: #6F9E5C; --red: #C0533F; + /* 🔴 v40(用户 2026-10-10):「影院管理部分…编辑使用蓝色的背景,删除使用红色的背景。」 + ⇒ 新增一个中性的"操作蓝"(不是链接蓝、也不是模块品牌色)—— 它是**语义色**: + 蓝=编辑、红=删除,全站统一,跟着模块换肤也不该变。 + ⚠️ 暖纸底上要够暗才压得住白字:这里取 #2F6FB0(对比度 ≈ 4.9:1)。 */ + --blue: #2F6FB0; --line: rgba(44, 35, 29, .10); --line-2: rgba(44, 35, 29, .06); --radius: 16px; @@ -1422,6 +1427,26 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb, .page-head .actions > a.btn-danger:not(.btn-icon):hover, .page-head .actions > form > .btn-danger:not(.btn-icon):hover, .page-head .actions > form > button.btn-danger:not(.btn-icon):hover { background: rgba(192, 83, 63, .09); } + +/* 层 4:**实心**底色按钮(v40 用户要求)。 + 原话(2026-10-10):「删除按钮就用红色背景颜色…影院管理部分…编辑使用蓝色的背景, + 删除使用红色的背景。」 + ⇒ 这两个要的是**实心色块**(一眼分得清),而不是层 3 那种淡色描边。 + ⚠️ 为什么不直接改 `.btn-danger`:全站十来处「删除」都在用它,改成实心会到处变样; + 而且**页头动作区的层 1**(`.actions > form > button.btn`,特异性 0,4,1) + 会盖过基类 `.btn-del`(0,1,0)—— 必须在同一层里再写一条才生效(与层 3 同一个坑)。 */ +.page-head .actions > .btn-del:not(.btn-icon), +.page-head .actions > a.btn-del:not(.btn-icon), +.page-head .actions > form > .btn-del:not(.btn-icon), +.page-head .actions > form > button.btn-del:not(.btn-icon) { + background: var(--red); color: #fff; border-color: transparent; +} +.page-head .actions > .btn-edit:not(.btn-icon), +.page-head .actions > a.btn-edit:not(.btn-icon), +.page-head .actions > form > .btn-edit:not(.btn-icon), +.page-head .actions > form > button.btn-edit:not(.btn-icon) { + background: var(--blue); color: #fff; border-color: transparent; +} /* 「设置」图标按钮:**唯一无色的**(白底描边、方形),与上面所有彩色按钮区分。 padding 归零、做成正方形,但高度必须仍是 --act-h(= 文字按钮的 43.8px)—— 写死过 40px,比旁边按钮矮 3.8px,是用户反复反馈的那个问题。 */ @@ -5323,3 +5348,81 @@ h5.md-h, h6.md-h { font-size: 14px; } (它们本来就只有一块,当时的"并排网格"里只有一个孩子, grid 让它只占半宽 —— 那正是"海报的上传框比简介窄"的原因)。 ⚠️ 不留悬念:全站搜 `.mv-pair` 应该**零命中**(模板与 CSS 都清干净)。 */ + +/* ============================================================================ + 🔴 v40(用户 2026-10-10)观影模块四处界面反馈 + ============================================================================ */ + +/* ---- ① 影院管理表格:操作列不再被挤成两行 ---- + 用户原话:「影院管理部分,注意控制列的宽度,因为最右侧的操作、编辑和删除两个按钮, + 已经被挤成两行了,尝试让编辑和删除排在一行,宽度给它留够。」 + 🔴 真因:`.table` 是 `width:100%` + 自动布局,浏览器按内容分配列宽 —— + 长地址那一列会把「操作」列压到只剩几十像素,两个按钮只好换行。 + ⇒ 两件事一起做才稳: + ① 表格改 `table-layout: fixed`(列宽由我们给,不再被内容牵着走); + ② 「操作」列给一个**够放两个按钮的最小宽度**(编辑 48 + 间距 8 + 删除 48 + + 单元格左右 padding 16 ≈ 128,留到 132 更从容)。 + ⚠️ 为什么不只是 `white-space:nowrap`:那会把列撑成"刚好够", + 但**表格还是按内容分配**,窗口一窄就又轮到别的列挤压它; + 固定布局才是真的把这件事钉死。 */ +.cn-table { table-layout: fixed; } +.cn-table .cn-col-name { width: 30%; } +.cn-table .cn-col-place { width: 15%; } +.cn-table .cn-col-addr { width: 32%; } +.cn-table .cn-col-count { width: 10%; } +.cn-table .cn-col-ops { width: 132px; } +.cn-table td.cn-addr { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.cn-ops { + display: flex; align-items: center; gap: 8px; + /* 🔴 不换行是本条的正题 */ + flex-wrap: nowrap; white-space: nowrap; +} +.cn-ops > form { display: inline-flex; margin: 0; flex: 0 0 auto; } +.cn-ops > .btn { flex: 0 0 auto; } +/* 窄屏:地址列让位(它最长、也最不影响判断),操作列宽度保持不变 */ +@media (max-width: 720px) { + .cn-table .cn-col-name { width: 34%; } + .cn-table .cn-col-place { width: 18%; } + .cn-table .cn-col-addr { width: 22%; } + .cn-table .cn-col-count { width: 12%; } +} + +/* ---- ② 实心语义按钮:编辑=蓝、删除=红 ---- + ⚠️ 单独定义(不去改 `.btn-danger` 的既有语义):`.btn-danger` 是**淡红描边**的 + "危险动作",全站十来处「删除」都在用它;用户这次要的是**实心色块**, + 改 `.btn-danger` 会让所有页面一起变样。 + 三处都要写:裸类 / `a.` / `button.` —— 与页头动作区那套同一个理由 + (`.btn`、`a.btn`、`button.btn` 特异性不同,漏一种就有一种是错的)。 */ +.btn-edit, a.btn-edit, button.btn-edit { + background: var(--blue); color: #fff; border-color: transparent; +} +.btn-del, a.btn-del, button.btn-del { + background: var(--red); color: #fff; border-color: transparent; +} +/* ⚠️ hover 统一交给文件靠后的「全站悬停统一」段(有底色的一律同色系加深)—— + 但那段是按 `.btn-ok/.btn-del/...` 的类名白名单写的,`.btn-edit` 是新的, + 要在这里补一条,否则它会退回 `.btn` 的默认(品牌色底),看着像变了个按钮。 */ +.btn-edit:hover { filter: brightness(.92); } + +/* ---- ③ 观影详情页 = 列表页的头部口径 ---- + 用户原话:「观影详情页面左上角的观影记录,应该和观影的列表页面里面保持一致。」 + 列表页是 `
还没有影院,先在上面添加一家。
| 名称 | 所在地方 | 详细地址 | 观影 | 操作 |
|---|---|---|---|---|
| 名称 | +所在地方 | +详细地址 | +观影 | +操作 | += e((string)($c['place'] ?? '')) !== '' ? e($c['place']) : '—' ?> | -= !empty($c['address']) ? e($c['address']) : '—' ?> | += !empty($c['address']) ? e($c['address']) : '—' ?> | = (int)$c['movie_count'] ?> 场 | -
-
- 编辑
+
+ 外部表),所以不用 !important。 */ ?>
+ |
diff --git a/templates/movies/form.php b/templates/movies/form.php
index ae948ad..fd48c91 100644
--- a/templates/movies/form.php
+++ b/templates/movies/form.php
@@ -75,18 +75,32 @@ $mvModalOnly = !empty($mvModalOnly); ?>
⚠️ 表单本身**一个字没动**(字段名 / 服务端契约 / CSRF / enctype 全照旧),
只是把它的容器从"页面上的卡片"换成"浮层里的面板"。 */ ?>
-
+ 编辑
>
+
>
+
diff --git a/templates/movies/report.php b/templates/movies/report.php
index ce72561..3cd41d3 100644
--- a/templates/movies/report.php
+++ b/templates/movies/report.php
@@ -71,12 +71,20 @@ $fmtDate = static function (?string $d): string {
- 记一场电影
+ = $isEdit ? '编辑观影记录' : '记一场电影' ?>
@@ -374,7 +388,7 @@ $mDelForms = []; // 收集起来,在表单**外面**统一渲染
-
+
月份分布 每个月看几场
$cnt): ?>
-
-
+
= (int)$m ?>月
= (int)$cnt ?>
@@ -90,7 +98,9 @@ $fmtDate = static function (?string $d): string {
diff --git a/templates/movies/search.php b/templates/movies/search.php
index 5c0e763..a556550 100644
--- a/templates/movies/search.php
+++ b/templates/movies/search.php
@@ -49,12 +49,19 @@
require __DIR__ . '/../_person_picker.php';
?>
+
+
-
diff --git a/templates/movies/show.php b/templates/movies/show.php
index 096f34a..74ea9b5 100644
--- a/templates/movies/show.php
+++ b/templates/movies/show.php
@@ -140,19 +140,34 @@ $commentBox = static function (int $reviewId, int $parentId, string $summary) us
};
?>
-
+
+
至
-
+
- 观影记录
+ 观影记录`(没有 h1)改成与列表页**同款**
+ `
@@ -557,3 +572,199 @@ $commentBox = static function (int $reviewId, int $parentId, string $summary) us
})();
+
+
+
+
+
+
+
diff --git a/templates/movies/stats.php b/templates/movies/stats.php
index 6fba8f0..16cc7c9 100644
--- a/templates/movies/stats.php
+++ b/templates/movies/stats.php
@@ -105,12 +105,17 @@ $wd = static function (?string $d): string {
观影记录`,片名降级到 sub —— 两页左上角看起来就是一件事; + ② 「编辑」「返回观影」用**裸 `.btn`**(= 列表页「记一场电影」那个品牌实心款); + ③ 「删除」用 `.btn-del`(**实心红底**,不是原来那支淡红描边)。 */ ?> +观影记录+ = e($m['title']) ?>月度趋势 最近 12 个月+ |