v41 第 41 批:流水线提速 + 12 项界面/交互开发 + DAV 彻底修复

DAV(用户「务必彻底解决」):
- 真根因是 home-set 层级错:CalDav::HOME_PATH 原为 /dav/calendars/、
  CardDav 原为 /carddav/addressbooks/,而日历本/通讯录的 href 在
  /dav/N.ics/ 与 /carddav/N.vcf/ —— home-set 下面一本都没有。
  Thunderbird 按 home-set 前缀认成员,于是「找不到日历本 / 找不到联系人」。
  改为 /dav/ 与 /carddav/(所有既有 URL 一字不变,老配置不作废)。
- 又发现并修掉 Depth:0 违反 RFC 4918:原实现无论 depth 都列出全部子集合,
  且第一条 response 是日历本(不含 current-user-principal)。改为按 depth 分流,
  Depth:0 只回资源自身、Depth:1 才列出子集合。

界面与交互:
- 观影:新建影院弹窗层级修正 + 详细地址;照片左右按钮悬停不再位移;
  弹框底栏改「确定 / 取消」。
- 旅程:新建旅程改弹窗并按观影式排版;详情页四个动作按钮按用户指定顺序与配色;
  编辑与新增行程弹窗化(单容器 + AJAX);行程时间控件改 AntD(精确到分);
  参与人默认带上自己;车次号/车牌号紧跟交通方式词。
- 资源:新建/编辑改弹窗 + 观影式排版 + 新建时即定共享人。
- 纪念日:新建改弹框 + 观影式排版。

验证:
- e2e 1510/0;v41 十二项真机 48/0;DAV 反向验证 8/0;
  部署断言空跑 337/0;模板 div 深度三形态穷举 33 组全配平。
- 线上 DAV 终验(--resolve 真实入口 + 真 DAV 口令)12/0;
  线上确认 Depth:0 恰好 1 个 response。
This commit is contained in:
2026-10-11 10:12:11 +08:00
parent f9e30bd7d8
commit 7544fb9f6a
19 changed files with 1616 additions and 342 deletions
+180 -2
View File
@@ -20,6 +20,15 @@
蓝=编辑、红=删除,全站统一,跟着模块换肤也不该变。
⚠️ 暖纸底上要够暗才压得住白字:这里取 #2F6FB0(对比度 ≈ 4.9:1)。 */
--blue: #2F6FB0;
/* 🔴 v41(用户 2026-10-10):旅程详情页四种动作按钮各一种底色
(计划=蓝 / 实际=绿 / 编辑=黄 / 删除=红)。蓝、绿(--green)、红已有;
这里补"编辑黄"。
⚠️ 纯黄(#FFD400 之类)配白字几乎看不清(对比度 ≈ 1.3:1)。
取偏琥珀的深黄 #B8860B(DarkGoldenrod 系),暖纸底上白字对比度 ≈ 3.6:1,
且不会和品牌暖棕 (`--brand` #A84A28) 混淆 —— 后者明显更红。
⚠️ "实际=绿"用既有的 `--green` (#6F9E5C),它在白字下对比度 ≈ 3.0:1,
深一步更保险 —— 见 `.btn-actual` 里单独取的 #4E7C3F(≈ 4.6:1)。 */
--yellow: #B8860B;
--line: rgba(44, 35, 29, .10);
--line-2: rgba(44, 35, 29, .06);
--radius: 16px;
@@ -1710,7 +1719,8 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
position: absolute; border: none; cursor: pointer; color: #fff;
background: rgba(255, 255, 255, .12); display: flex;
align-items: center; justify-content: center;
transition: background-color .2s ease; font-family: inherit;
transition: background-color .2s ease, filter .2s ease;
font-family: inherit;
}
.lb-btn:hover { } /* 深底上不能用薄雾:走文末统一段的 brightness 提亮 */
.lb-btn[hidden] { display: none; }
@@ -1723,6 +1733,17 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
}
.lb-dl:hover { background: #eee; }
.lb-nav { top: 50%; transform: translateY(-50%); width: 48px; height: 48px; border-radius: 50%; font-size: 28px; padding-bottom: 4px; }
/* 🔴🔴 v41(用户 2026-10-10):「观影照片的左右按钮,鼠标悬停之后位置会变一下,
一不小心就会点错。」
真根因:文末「全站悬停统一」段给 `.lb-btn:hover` 加了
`transform: translateY(var(--hov-lift))`(= 上移 1px 的"轻抬升"手感)——
而 `.lb-nav` 是靠 `transform: translateY(-50%)` 做**垂直居中**的,
hover 那条**把居中的 transform 整个覆盖掉** ⇒ 鼠标一碰,按钮从"居中"跳到
"距顶 50%",**位移接近半个按钮高度**(48px 的一半),确实会点错。
⚠️ 与 v21 家谱那次 `.f3 div.card:hover > div` 覆盖库的居中 transform 是**同一个坑**。
⇒ 修:`.lb-nav:hover` 显式把它自己的居中 transform 写回去(只保留提亮,
不再抬升)。位置在 hover 前后**一字不动**,鼠标停在哪就点在哪。
⚠️ 必须写在统一 hover 段**之后**(同特异性下后者胜)—— 见文末那段里的同名规则。 */
.lb-prev { left: 14px; }
.lb-next { right: 14px; }
.lb-bar {
@@ -2942,7 +2963,14 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
/* 新建影院:居中弹框(与名册的动作弹框同一形态,只是更宽) */
.cn-modal {
position: fixed; inset: 0; z-index: 70;
/* 🔴🔴 v41(用户 2026-10-10):「观影模块,第一场电影,新建影院这个弹窗不显示,
被记一场电影这个弹窗盖住了。」
真根因:`.cn-modal` 是 **70**,而「记一场电影」用的 `.cal-modal` 是 **200** ——
影院弹框是**从观影弹框里**打开的子层,层级却更低 ⇒ 被整块盖住、看不见也点不到。
⇒ 提到 260:必须**高于 .cal-modal(200)**,也高于 antd 的下拉面板
(`.ant-picker-dropdown` 走 popper,默认 1050 之外的那一层我们没碰;
这里只需保证"压过所有自建弹框"即可)。 */
position: fixed; inset: 0; z-index: 260;
display: flex; align-items: center; justify-content: center;
}
.cn-modal[hidden] { display: none; }
@@ -3784,6 +3812,12 @@ button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-b
filter: brightness(1.18);
transform: translateY(var(--hov-lift));
}
/* 🔴🔴 v41 例外(必须写在上面那条**之后**,靠"同特异性、后者胜"生效):
`.lb-nav`(照片左右切换)是**靠 transform 居中**的,一旦被上面的
`translateY(var(--hov-lift))` 覆盖,hover 时按钮会从"居中"跳到"距顶 50%",
位移近半个按钮高 —— 用户报的就是这个("悬停之后位置会变一下,一不小心就会点错")。
⇒ 这里把它的居中 transform 显式写回,只保留提亮。位置前后一字不动。 */
.lb-nav:hover { transform: translateY(-50%); }
/* v24 第 20 批 第 3 条:`form="id"` 关联用的**表单外壳**。
它们只承载字段、不出现在视觉里(按钮在原来的位置上,用 form= 指过来)。
@@ -5426,3 +5460,147 @@ h5.md-h, h6.md-h { font-size: 14px; }
/* 纯日期版控件与观影那边同款外观(同高、同圆角、同描边)—— 复用 .dt-antd 的规则,
这里只补"它是块级、占满字段宽度"这一点(.dt-antd 原为 flex 子项)。 */
.dt-antd-dateonly { display: block; width: 100%; }
/* ============================================================================
🔴 v41(用户 2026-10-10)旅程详情页的四个动作按钮:计划=蓝 / 实际=绿 / 编辑=黄 / 删除=红
============================================================================
为什么四种都要在**页头动作区那一层**再写一遍:
页头有一套"特异性更高"的规则(`.page-head .actions > .btn:not(.btn-icon)` 等,
见文件前部的页头按钮层),它们会盖过基类的 `.btn-xxx` 底色。
v40 的 `.btn-del` 就吃过这一脚(必须在页头层再补一条才生效)。
⚠️ 三处形态都要写(裸类 / a. / button.):`.btn`、`a.btn`、`button.btn`
的特异性不同,漏一种就有一种是错的(这条规律在 v40 已经验证过)。
⚠️ 设置按钮**不动**(用户:"设置就用这个图标,不要变")。 */
.btn-plan, a.btn-plan, button.btn-plan {
background: var(--blue); color: #fff; border-color: transparent;
}
.btn-actual, a.btn-actual, button.btn-actual {
/* 绿比 --green 深一档:白字对比度 3.0 → 4.6,暖纸底上看得清 */
background: #4E7C3F; color: #fff; border-color: transparent;
}
/* 🔴🔴 2026-10-11 修(v40 套件报的**真回归**):
这里原来写的是**裸类** `.btn-edit`(全局改黄),直接把 v40 影院管理页的
「编辑 = 蓝色实心」覆盖掉了 —— 影院那页量出来的实际色是 `rgb(184,134,11)`(黄),
而用户对它的要求是**蓝色**(v40 原话:「编辑使用蓝色的背景,删除使用红色的背景」)。
⇒ 两处需求不同:**旅程详情页的「编辑」= 黄**(v41 第 4 条),
**影院管理页的「编辑」= 蓝**(v40)。所以颜色必须**按作用域**给,不能裸类全局设。
锚点选 `[data-trip-edit]`(旅程详情页那个按钮的专属属性)——
它是最精确的标识,不依赖页头层级,也不会波及影院管理页的 `<a class="btn btn-edit">`。 */
.btn-edit[data-trip-edit] {
background: var(--yellow); color: #fff; border-color: transparent;
}
.btn-del, a.btn-del, button.btn-del {
background: var(--red); color: #fff; border-color: transparent;
}
/* hover:同色系加深(与文末"全站悬停统一"段同一手法:brightness 压低) */
.btn-plan:hover, .btn-actual:hover, .btn-edit:hover, .btn-del:hover { filter: brightness(.92); }
/* 页头动作区那一层(覆盖页头默认的"品牌色实心/ghost") */
.page-head .actions > .btn-plan:not(.btn-icon),
.page-head .actions > .btn-actual:not(.btn-icon),
.page-head .actions > .btn-edit[data-trip-edit]:not(.btn-icon),
.page-head .actions > .btn-del:not(.btn-icon),
.page-head .actions > form > button.btn-del:not(.btn-icon) { color: #fff; border-color: transparent; }
.page-head .actions > .btn-plan:not(.btn-icon) { background: var(--blue); }
.page-head .actions > .btn-actual:not(.btn-icon) { background: #4E7C3F; }
.page-head .actions > .btn-edit[data-trip-edit]:not(.btn-icon) { background: var(--yellow); }
.page-head .actions > .btn-del:not(.btn-icon),
.page-head .actions > form > button.btn-del:not(.btn-icon) { background: var(--red); }
/* ---- v41 第 8 条:交通方式的"标识"(车次号 / 车牌号)紧跟模式词 ----
用户:「把火车的车次号直接放在"火车"这两个字后面。如果是自驾,
就应该把自驾的车牌号放在后面。」
⇒ 同一个 chip 内、模式词之后,用略淡的字重区分(仍属同一个信息单元)。 */
.node-mode-no {
margin-left: 6px; padding-left: 6px;
border-left: 1px solid currentColor;
opacity: .78; font-weight: 500;
font-variant-numeric: tabular-nums;
}
/* ---- v41 第 2 条:题图的说明文字搬进框里(原来在框外另起一段)---- */
.cover-picker-hint {
display: block; margin-top: 8px;
font-size: 12.5px; line-height: 1.6; color: var(--muted);
}
/* ============================================================================
🔴 v41(用户 2026-10-10 第 5/6 条)行程弹框的**观影式排版**
============================================================================
用户原话:「编辑行程、加计划行程、加实际行程,全部都采用弹窗的形式。
其排版的方式和添加电影是一样的,前面是标题冒号,后面是输入框,
和观影模块一样,注意排版。」
🔴🔴 为什么这里用 **grid** 而不是 flex(2026-10-10 血泪):
先按观影那套写了 flex(`display:flex; flex-wrap:wrap` + label 固定 62px),
结果**控件被挤到下一行**(实测 label y=117 / 控件 y=187)。
查了半天:`.input/.select` 全站有 `width: 100%`,在 flex 行里
它的 auto 基准就是**整行宽** ⇒ 62 + 10 + 696 > 696 ⇒ 换行。
把 width 改 auto、把 flex-basis 从 0% 试到 auto、加 min-width……
控件宽度是对了(140),**换行依旧**(同一行里还有 `.tip` 声明着
`flex: 0 0 100%`,wrap 算法会把它们的理想宽度一起算)。
⇒ 换 **grid**:`.field` 两列(62px + 1fr),标签在第 1 列、控件在第 2 列,
`.tip/.err` 用 `grid-column: 2` 自动落在控件下面**且与控件左缘对齐**
(连 margin-left 都不需要)。两列表单的标准写法,不受 width/flex 交互影响。
⚠️ 作用域用 `.nf-form`(表单自己的类)而不是 `.nf-wrap` / `.nf-panel`:
片段形态(`?embed=1`)两者都不输出(壳由详情页容器给),
只用它们的话排版在弹框里**完全不生效**(探针抓到过)。 */
.nf-form .form-grid {
display: flex; flex-direction: column; gap: 12px;
}
.nf-form .form-grid > .field {
display: grid; grid-template-columns: 62px minmax(0, 1fr);
column-gap: 10px; row-gap: 6px; align-items: start;
}
/* 标签:固定第 1 列、右对齐、自动冒号(与观影那套同一组数字) */
.nf-form .form-grid > .field > label {
grid-column: 1;
text-align: right; padding-top: 9px;
font-size: 14px; font-weight: 600; color: var(--ink);
line-height: 1.4; word-break: keep-all;
}
.nf-form .form-grid > .field > label::after {
content: ':'; font-weight: 400; color: var(--muted);
}
/* 控件:占第 2 列(宽度由 grid 给,不受它的 width:100% 影响) */
.nf-form .form-grid > .field > *:not(label) {
grid-column: 2; min-width: 0;
}
/* 提示 / 错误:也在第 2 列 ⇒ **自动与控件左缘对齐** */
.nf-form .form-grid > .field > .tip,
.nf-form .form-grid > .field > .err {
grid-column: 2;
}
/* 起终点是两列小网格,整段落在第 2 列 */
.nf-form .form-grid > .field > .loc-row { grid-column: 2; }
/* 「当前状态」是只读文本,对齐 40px 高的控件 */
.nf-form .node-stage-ro { padding-top: 9px; }
/* 窄屏:标签列压到 52px,避免控件被挤得太窄 */
@media (max-width: 560px) {
.nf-form .form-grid > .field { grid-template-columns: 52px minmax(0, 1fr); }
}
/* ---- v41 第 8 条:交通方式的"标识"(车次号 / 车牌号)紧跟模式词 ----
用户:「把火车的车次号直接放在"火车"这两个字后面。如果是自驾,
就应该把自驾的车牌号放在后面。」
⇒ 同一个 chip 内、模式词之后,用略淡的字重区分(仍属同一个信息单元)。 */
.node-mode-no {
margin-left: 6px; padding-left: 6px;
border-left: 1px solid currentColor;
opacity: .78; font-weight: 500;
font-variant-numeric: tabular-nums;
}
/* ---- v41 第 2 条:题图的说明文字搬进框里(原来在框外另起一段)---- */
.cover-picker-hint {
display: block; margin-top: 8px;
font-size: 12.5px; line-height: 1.6; color: var(--muted);
}
/* ---- v41 第 10 条:纪念日弹框(观影式排版)里"月 / 日"那一行的两格 ---- */
.an-md { display: flex; align-items: center; gap: 6px; }
.an-md .input { width: 88px; flex: 0 0 auto; text-align: center; }
.an-md-sep { color: var(--muted); font-size: 13px; }
/* 观影式行里的"填充块"(选人组件那种自带布局的控件,占满控件位) */
.mv-row > .mv-fill { flex: 1 1 auto; min-width: 0; }