v11: 家谱按户排布(夫妻并排/长幼排序/父母括号/姻亲);观影设置下拉收拢影院·搜索·统计·年度报告;观影人头像

家谱(用户第 2 点,重写模板与 Kinship::tree):
- 同代聚成「夫妻单元」(户):血亲当主位、配偶并排在其右,兄弟_姐妹按长幼(长子左)
- 血亲可达性判「姻亲」:只走 parent/child 边从「我」可达者为本家血亲,妻子的父母
  走不到 → 标「姻亲」并排到本代末尾(旧判据「上无父母且下无子女」对岳父母恒为假,是真 bug)
- 父母括号连线:横杆盖住父母列到其子女各列,竖线落父母列中心、每子女列一道下线
- 称呼按长幼定死:姐夫/妹夫、嫂子/弟媳、大/小舅子、大/小姨子、大伯子/小叔子、
  大姑子/小姑子、侄子/侄女、外甥/外甥女、堂/表
- 列宽地板 minmax(min-content,1fr):原 minmax(0,1fr) 在窄屏+户数多时把列压到卡片
  (96px)以下 → 同代卡片互相重叠;.tnode 加 flex:0 0 auto 防被 flex 压扁

观影(用户第 3~5 点):
- 顶栏删「影院」入口(影院是观影模块的子功能);观影列表右侧加「⚙ 设置」下拉,
  内含 影院管理 / 高级搜索 / 统计 / 年度报告(后三者从 MovieLog 迁入,
  app/MovieStats.php + 三个只读路由,MySQL 方言;图表全用纯 CSS,不引 CDN)
- 评价区删除「我」的长条徽标(被 flex column stretch 拉成整行宽的丑色带)
- 观影人弹窗列表与已选 chips 带头像;syncChips 点名取 .att-name/.att-av
  (旧写法取「第一个 span」,v11 起那是隐藏占位,chip 上会没名字)

验证:e2e 432/0、桌面几何 268/0、移动几何 464/0、交互几何 97/0、
静态空跑 199/0、反向验证 v11 十条 10抓/0漏。本轮无 DB 变更(Schema 仍 v10)。
移动几何抓出两个真溢出并已修:高级搜索页 select 长选项撑破 .field、
原生 date 输入撑破 .date-range(360/375px)。
This commit is contained in:
2026-10-01 12:11:43 +08:00
parent 2c36335c3e
commit e9b3b8666b
12 changed files with 1494 additions and 103 deletions
+202 -20
View File
@@ -100,6 +100,16 @@ a:hover { text-decoration: underline; }
border-radius: 0 0 8px 8px; color: var(--muted);
}
.menu-list .menu-exit:hover { color: var(--brand); }
/* ---- 观影页的「设置」下拉(v11):一个齿轮图标 + <details> 菜单 ---- */
.movie-set { position: relative; }
.movie-set > summary {
list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
}
.movie-set > summary::-webkit-details-marker { display: none; }
.movie-set[open] > summary { border-color: var(--brand); color: var(--brand); }
/* 观影设置菜单是顶栏菜单的复用:位置从右上角改成按钮下方左对齐 */
.movie-set .menu-list { left: 0; right: auto; top: calc(100% + 8px); }
.movie-set .menu-list a.on { color: var(--brand); background: var(--line-2); }
.link-quiet { color: var(--muted); font-size: 14px; }
.link-quiet:hover { color: var(--brand); }
@@ -224,7 +234,11 @@ a:hover { text-decoration: underline; }
/* ---------------- 表单 ---------------- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.field { display: flex; flex-direction: column; gap: 6px; }
/* ⚠️ min-width:0 是必需的(v11 实测踩到):.field 是 grid 项,默认 min-width:auto
会被内部 <select> 的**最长选项文字**撑开 —— 高级搜索页在 360/375px 下整页横向溢出
(scrollW 390 > clientW 360),而「影院」下拉正是长选项。给了 0 之后列才能收进版心,
下拉自身由 width:100% 跟着收。与 .hero-main / .person-move .select 同一套做法。 */
.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field.full { grid-column: 1 / -1; }
.field label { font-size: 13px; color: var(--muted); font-weight: 500; }
.field .tip { font-size: 12px; color: var(--muted); }
@@ -366,9 +380,20 @@ textarea.input { min-height: 76px; resize: vertical; }
margin-right: 2px;
}
/* ---------------- 家谱(以我为中心) ---------------- */
/* ---------------- 家谱(以我为中心) ----------------
v11 改版:每代是「夫妻单元」网格 + 父母括号连线。
所有代的列网格共用同一份 template(宽度取最宽那一代),连线才能跟人对齐。 */
.tree { display: flex; flex-direction: column; align-items: center; gap: 0; }
.tree-gen { width: 100%; }
.tree-inner {
width: 100%;
overflow-x: auto; /* 家里人再多也不挤:横向滚,不改版心 */
-webkit-overflow-scrolling: touch;
padding-bottom: 4px;
}
/* ⚠️ 必须是 min-content 而不是 100%:min-content 让「代」按内容自然展开,
.tree-inner 才有东西可滚;列的地板由 .tree-row 的 minmax(min-content,1fr) 兜。
两处一起才成立 —— 单改一处会在窄屏下把卡片压重叠(v11 实测踩到)。 */
.tree-gen { min-width: min-content; }
.tree-gen-label {
display: flex; align-items: center; gap: 8px;
margin: 0 0 8px; padding-left: 2px;
@@ -378,13 +403,28 @@ textarea.input { min-height: 76px; resize: vertical; }
font-size: 11px; color: var(--muted); background: var(--line-2);
padding: 1px 8px; border-radius: 999px;
}
/* 一代 = 若干「户」,每户占网格的一列(列数由 inline grid-template-columns 给) */
.tree-row {
display: flex; flex-wrap: wrap; gap: 10px;
justify-content: center; align-items: stretch;
display: grid; gap: 14px;
align-items: start;
}
.tree-unit {
display: flex; align-items: center; justify-content: center; gap: 0;
position: relative; padding: 0 2px;
}
/* 夫妻之间的短横(儿女亲家那种「连着的两个人」) */
.tree-bond { width: 14px; flex: 0 0 auto; display: flex; align-items: center; }
.tree-bond i {
display: block; width: 100%; height: 2px; border-radius: 2px;
background: var(--line); transform: translateY(2px);
}
.tnode {
display: flex; flex-direction: column; align-items: center; gap: 5px;
width: 104px; padding: 12px 8px 10px; text-align: center;
/* ⚠️ flex:0 0 auto 是必需的(v11 实测踩到):.tnode 是 .tree-unit(flex)的子项,
默认 flex-shrink:1 会让卡片**先被压**,列地板的 min-content 也跟着变小 ——
等于把「列不许压到卡片以下」这条设计从内部掏空了。卡宽由下面的 width 定,
窄屏另有 78px 的媒体查询(那是刻意的窄版式,不是压缩)。 */
flex: 0 0 auto; width: 96px; padding: 12px 6px 10px; text-align: center;
background: var(--surface); border: 1px solid var(--line);
border-radius: 14px; box-shadow: var(--shadow);
color: var(--ink); transition: transform .15s ease, box-shadow .15s ease;
@@ -401,11 +441,33 @@ textarea.input { min-height: 76px; resize: vertical; }
}
.tnode.is-me .tlabel { color: var(--brand-ink); font-weight: 600; }
.tnode.is-placeholder { border-style: dashed; }
/* 世代之间的连接线(纯装饰,说明"往下走一代") */
.tree-link {
width: 2px; height: 22px; background: linear-gradient(var(--line), var(--line-2));
margin: 6px auto;
/* 因婚姻进图、上下都不接血的旁支(如妻子的父母):虚线边框 + 角标,免得像断线 */
.tnode.is-inlaw { border-style: dashed; background: #FCFAF6; }
.tree-inlaw-tag {
position: absolute; top: -8px; right: 0;
font-size: 10px; color: var(--faint); background: var(--paper);
border: 1px solid var(--line); border-radius: 999px; padding: 0 6px;
}
/* ---- 父母括号连线 ----
.tc-bar = 横杆(占父母列 + 子女各列,居中画一根竖线上去接父母)
.tc-down = 每个子女列的一道竖线(接进同一个横杆)*/
.tree-conn {
display: grid; gap: 14px; height: 30px; position: relative;
}
.tc-bar { position: relative; align-self: end; border-top: 0; }
.tc-bar::before {
content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
background: var(--line); border-radius: 2px;
}
.tc-up {
position: absolute; bottom: 0; width: 2px; height: 14px;
background: var(--line); transform: translateX(-1px);
}
.tc-down {
align-self: end; justify-self: center;
width: 2px; height: 30px; background: var(--line);
}
.tree-legend { font-size: 12px; color: var(--muted); margin: 14px 2px 0; }
/* 详情页里的迷你家谱 */
.mini-tree { display: flex; flex-direction: column; gap: 8px; }
@@ -473,9 +535,12 @@ textarea.input { min-height: 76px; resize: vertical; }
@media (max-width: 720px) {
.rel-form { grid-template-columns: 1fr; }
.tnode { width: 88px; padding: 9px 6px 8px; }
.tnode .tph { width: 44px; height: 44px; }
.tnode { width: 78px; padding: 8px 4px 7px; }
.tnode .tph { width: 40px; height: 40px; }
.tree-row { gap: 8px; }
.tree-unit { padding: 0; }
.tree-bond { width: 8px; }
.tree-conn { gap: 8px; }
}
@media (max-width: 720px) {
@@ -977,14 +1042,18 @@ textarea.input { min-height: 76px; resize: vertical; }
.stars-disp { display: inline-flex; gap: 1px; font-size: 17px; line-height: 1; vertical-align: -2px; }
.stars-disp .d-on { color: #f5b301; }
.stars-disp .d-off { color: #d3d7de; }
/* ---- 同行观影人选择(v10):chips + 弹窗 + 拼音首字母联想 ---- */
/* ---- 同行观影人选择(v10):chips + 弹窗 + 拼音首字母联想(v11 起带头像) ---- */
.att-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.att-chips:empty { display: none; }
.att-chip {
display: inline-flex; align-items: center; gap: 6px; padding: 3px 6px 3px 10px;
display: inline-flex; align-items: center; gap: 6px; padding: 3px 6px 3px 4px;
border: 1px solid var(--line); border-radius: 999px; background: var(--surface);
font-size: 13px;
}
.att-chip img {
width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto;
background: #F1E7D6;
}
.att-chip .att-x {
border: 0; background: none; color: var(--muted); font-size: 15px;
line-height: 1; padding: 0 2px; cursor: pointer;
@@ -1013,7 +1082,12 @@ textarea.input { min-height: 76px; resize: vertical; }
}
.att-item:last-child { border-bottom: 0; }
.att-item:hover { background: var(--line-2); }
.att-item input { accent-color: var(--brand); }
.att-item input { accent-color: var(--brand); flex: 0 0 auto; }
.att-item .att-av {
width: 30px; height: 30px; border-radius: 50%; object-fit: cover; flex: 0 0 auto;
background: #F1E7D6;
}
.att-item .att-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.att-item .att-initials { margin-left: auto; color: var(--muted); font-size: 12px; }
.att-empty { padding: 10px 12px; color: var(--muted); font-size: 13px; }
/* ---- 豆瓣自动补充(v10):片名行 + 下拉候选 + 海报预览 ---- */
@@ -1081,11 +1155,10 @@ textarea.input { min-height: 76px; resize: vertical; }
}
.rv-who { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.rv-name { font-size: 14px; font-weight: 600; color: var(--ink); }
.rv-mine {
display: inline-block; vertical-align: middle; margin-left: 6px;
font-size: 11px; line-height: 16px; background: var(--brand);
color: #fff; border-radius: 6px; padding: 0 6px;
}
/* ⚠️ 写着「我」的那个徽标(类名是「评价」区块 + 「我的」后缀)v11 删掉了:
它在 .rv-who(flex column)里被 align-items:stretch 拉成整行宽的色带,
星星旁边显出一条丑长的品牌色条。删除按钮已经表达了「这是我写的」,
不需要徽标。别再把它加回来。 */
.rv-time { display: block; font-size: 12px; color: var(--muted); }
.rv-score { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px; }
.rv-score .stars-disp { font-size: 16px; }
@@ -1177,3 +1250,112 @@ textarea.input { min-height: 76px; resize: vertical; }
.lb-next { right: 6px; }
.lb-img { max-width: 88vw; max-height: 70vh; }
}
/* ============================================================
v11:观影统计 / 高级搜索 / 年度报告(从 MovieLog 迁入)
版式沿用家书的暖纸配色;图表全用 CSS(不引 CDN 图表库)。
============================================================ */
/* ---- 统计仪表板 ---- */
.stats-grid {
display: grid; gap: 14px; margin-bottom: 16px;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.stat-card {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
padding: 16px 18px; text-align: center; box-shadow: var(--shadow);
}
.stat-card .number { font-size: 30px; font-weight: 700; color: var(--brand); line-height: 1.2; font-variant-numeric: tabular-nums; }
.stat-card .label { font-size: 13px; color: var(--muted); margin-top: 2px; }
.stats-span {
display: flex; align-items: baseline; gap: 12px; margin-bottom: 16px;
}
.stats-span .label { font-size: 13px; color: var(--muted); }
.stats-span .value { font-size: 16px; font-weight: 600; }
.chart-card {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
padding: 18px 22px; box-shadow: var(--shadow); margin-bottom: 16px;
}
.chart-card h3 { font-size: 16px; font-weight: 600; margin: 0 0 14px; display: flex; align-items: baseline; gap: 8px; }
.chart-card h3 .hint { font-size: 12px; color: var(--faint); font-weight: 400; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 720px) { .two-col { grid-template-columns: 1fr; } }
.bar-chart { display: flex; flex-direction: column; gap: 8px; }
.bar-item { display: flex; align-items: center; gap: 12px; }
.bar-item .bar-label {
width: 150px; flex: 0 0 auto; font-size: 13px; color: var(--ink);
display: flex; align-items: center; gap: 6px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bar-item .bar-label .bar-sub { color: var(--faint); font-size: 11px; }
.bar-item .bar-avatar { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.bar-item .color-dot { display: inline-block; width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
.bar-item .bar-track {
flex: 1; height: 20px; background: var(--line-2); border-radius: 6px; overflow: hidden;
}
.bar-item .bar-fill {
display: block; height: 100%; border-radius: 6px; background: var(--brand);
}
.bar-item .bar-fill-alt { background: var(--brand-2); }
.bar-item .bar-fill-green { background: var(--green); }
.bar-item .bar-count {
width: 40px; flex: 0 0 auto; text-align: right; font-size: 13px; font-weight: 600;
font-variant-numeric: tabular-nums;
}
@media (max-width: 720px) {
.bar-item .bar-label { width: 92px; font-size: 12px; }
.bar-item .bar-count { width: 30px; }
.chart-card { padding: 14px 16px; }
}
/* ---- 高级搜索 ---- */
.adv-form { margin-bottom: 18px; }
.date-range { display: flex; align-items: center; gap: 8px; }
.date-range .to { color: var(--muted); font-size: 13px; flex: 0 0 auto; }
/* ⚠️ 原生 <input type="date"> 的 min-width:auto 约 150px(带日历图标 + 占位数字)——
两个日期框 + 「至」在 360px 下会横向溢出(实测 scrollW 390 > clientW 360)。
给 0 并让它们等比收缩;这是继 .field(select)之后同一页的第二个溢出源。 */
.date-range .input { flex: 1 1 0; min-width: 0; }
.results-head {
display: flex; align-items: baseline; justify-content: space-between;
gap: 10px; margin: 0 2px 12px;
}
.results-head h3 { margin: 0; font-size: 17px; }
.results-head .sub { color: var(--muted); font-size: 13px; }
/* ---- 年度报告 ---- */
.year-pick { display: inline-block; }
.report-grid {
display: grid; gap: 14px; margin-bottom: 16px;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}
.report-grid.two { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.report-card {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
padding: 18px 20px; box-shadow: var(--shadow);
}
.report-card .n { font-size: 32px; font-weight: 700; color: var(--brand); line-height: 1.15; font-variant-numeric: tabular-nums; }
.report-card .l { font-size: 13px; color: var(--muted); }
.report-card .v { font-size: 19px; font-weight: 600; margin-top: 2px; }
.report-card .s { font-size: 12px; color: var(--faint); margin-top: 4px; }
/* 月份竖柱图(纯 CSS) */
.vgrid {
display: grid; grid-template-columns: repeat(12, 1fr); gap: 6px;
align-items: end; height: 160px; margin-top: 4px;
}
.vcol { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; gap: 3px; }
.vcol .vbar {
width: 68%; min-height: 2px; background: linear-gradient(var(--brand-2), var(--brand));
border-radius: 5px 5px 2px 2px; transition: height .4s ease;
}
.vcol .vlab { font-size: 11px; color: var(--muted); }
.vcol .vnum { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.kw-cloud { display: flex; flex-wrap: wrap; gap: 8px; }
.kw-cloud .kw {
font-size: 14px; padding: 4px 12px; border-radius: 999px;
background: var(--line-2); color: var(--brand-ink); border: 1px solid var(--line);
}