v42 第 42 批:十一条反馈(界面 9 条 + CalDAV 两处新根因)

CalDAV(用户「还是不行,thunderbird 提示找不到日历」):
- 🔴 `.well-known/caldav` 原来**只回 301**。Thunderbird 建账号时常是
  **带鉴权直接 PROPFIND** 这个地址(把它当 DAV 服务入口探),而它对这一跳的
  301 **不跟随** ⇒ 判定"这里没有 CalDAV" ⇒ 找不到日历。
  ⇒ 按方法分流:带鉴权 PROPFIND → 直接 207 给发现内容(principal / home-set /
    日历本清单,一步到位);其余仍是 301。carddav 同修。
- 🔴 `Location` 由**相对** `/dav/` 改成**绝对 URL**(按真实 HTTP_HOST 拼,
  协议从 HTTPS / X-Forwarded-Proto 取)。TB 在服务发现这一跳对相对跳转不可靠。
- 线上终验:匿名 GET 301+绝对URL;带鉴权 PROPFIND 直接 207(含 principal/home-set);
  Depth:0 只回自身;Depth:1 列出 12 本日历。

界面九条:
- ① 新建旅程不再带入旧数据(真根因:`_list_body.php` 的 `foreach ($rows as $t)`
  循环后 `$t` 残留,而 `form.php` 读的正是 `$t` ⇒ 弹框被填上列表最后一趟的数据)。
  修:循环变量改 `$row` + `form.php` 契约兼容(`$tIn` 优先)。
- ②③④ 行程弹框里的 AntD 时间控件 / 出行方式联动车牌与车次 / 高德地址联想全部失效
  (真根因:AJAX 片段里的内联 `<script>` **既不执行也不保留**)。
  ⇒ 加 `runInlineScripts()` 把片段内联脚本抠出来执行;
  另修 `app.js`:`if (!hasMonth) return` 的短路让 `window.fbBootAntd` 从未被定义,
  把它提到所有 return 之前。
- ④b⑤ 标签改「照片」「时间范围」。
- ⑥ 简介补"还没有简介,点这里写一句"的占位(简介本身一直在,是那条 summary 为空)。
- ⑦ 资源编辑复用新建弹框(`#resAddCard`),并在其底栏补「共享」「附件」入口
  (第 7 条改动后这两个能力一度无入口)。
- ⑧ 纪念日「月/日」→「日期」+ AntD 纯日期控件(新增 mountAntdDate / [data-antd-date]),
  存储契约仍是 month + day。
- ⑨ 编辑纪念日弹框关不掉(关闭逻辑原来只在 days/index.php,而 days/edit 是整页)
  ⇒ 搬进 days/form.php 自包含。

验证:e2e 1510/0;v42 真机 33/0;DAV·TB 35/0;DAV 反向 8/0;
b23 55/0;b28 39/0;b32 26/0;b32 反向 5/0;v31 DAV 28/0;b31 反向 8/0;
部署断言空跑 337/0;完整流水线 PUSH_RC=0。
This commit is contained in:
2026-10-11 18:35:03 +08:00
parent 7544fb9f6a
commit 81dfacaf1b
10 changed files with 612 additions and 44 deletions
+51
View File
@@ -169,6 +169,23 @@ $groupMeta = [
`_tooling/_fb_b19_backfill.php` 一次性搬进 summary。 */ ?>
<?php if (!empty($t['summary'])): ?>
<p class="info-text trip-sum"><?= nl2br(e((string)$t['summary'])) ?></p>
<?php elseif ($canWrite): ?>
<?php /* 🔴 v42(用户 2026-10-11 第 6 条):「旅程详情页面,参与的人下面,
原来有旅程的简介,怎么没了?加回来。」
⚠️ 排查结论:简介**一直在**(上面的 `!empty($t['summary'])` 分支),
结构从 v40 到 v41 一字未动(v41 只把「参与的人」后面那个括号
连同里面的说明整句去掉了)。
⇒ 用户"看不到"的真实原因是**那条旅程的简介是空的** ——
他多半是先用 v41 那个"新建旅程带入了旧数据"的 bug(本批第 1 条)
建/改过,summary 没存进去。
⇒ 修法两层:
① 第 1 条那个 bug 已修(新建不再串数据);
② 这里补一句**可点的占位**:简介为空但能写的人,看到"还没有简介,
点这里写一句" —— 否则整块是空的,用户会以为"功能被删了"。 */ ?>
<p class="trip-sum-empty">
还没有简介 ——
<a href="<?= e(url('trips/edit', ['id' => (int)$t['id']])) ?>">点这里写一句</a>。
</p>
<?php endif; ?>
<?php /* 老数据兜底(v24 第 19 批 第 4 条):只填过「备注」、没填过「简介」的旅程,
@@ -513,6 +530,7 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
/* 动态插入的控件:日期 / antd / 选人 / 上传都要在这一刻升级 */
if (window.fbUpgradeRange) { window.fbUpgradeRange(); }
if (window.fbBootAntd) { window.fbBootAntd(); }
if (window.fbNodeForm) { window.fbNodeForm(body() || document); }
var f = document.getElementById('nf-title');
if (f) { f.focus(); }
} else if (body()) {
@@ -525,6 +543,33 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
}
/* ---- 打开:从 data-node-open / data-trip-edit 拿 URL,取片段塞进来 ---- */
/* 🔴🔴 v42(用户 2026-10-11 第 2/3/4 条)——**根因级修法**:
AJAX 塞进来的片段里,`<script>` **既不执行、连元素都不保留**
(`innerHTML` 的语义:脚本不会跑,且赋值后 DOM 里也找不到那个 script 标签)。
片段自带的初始化(AntD 升级 / 高德联想 / 出行方式联动)全写在那段脚本里
⇒ 用户在弹框里看到的是:
· 起止时间没换成 AntD 控件(第 2 条);
· 选「自驾」不出车辆、选「火车/飞机」不出车次(第 3 条);
· 出发地/到达地没有高德联想(第 4 条)。
⇒ 做法:**把片段里的内联 `<script>` 抠出来,重新平成一个能执行的脚本**。
这是"动态插入含脚本的 HTML"的标准修法;比"在宿主里逐个复刻片段的初始化"
可靠得多 —— 片段将来再加初始化时,宿主不用跟着改。
⚠️ 只执行**内联**脚本(有 src 的是外部 SDK,设了 src 也不会同步执行,跳过即可;
高德那支是 `<script src>`,它的加载写在片段里由 SDK 自己完成)。
⚠️ 幂等:同一片段重复塞入时重新执行没问题(初始化内部都有幂等标记)。 */
function runInlineScripts(container, html) {
var re = /<script\b[^>]*>([\s\S]*?)<\/script>/gi, m, code = '';
while ((m = re.exec(html)) !== null) {
// 只收**没有 src 属性**的那种(内联脚本)
if (!/\bsrc\s*=/i.test(m[0].split('>')[0])) { code += m[1] + '\n;\n'; }
}
if (!code) { return; }
var s = document.createElement('script');
s.textContent = code;
(container || document.body).appendChild(s); // 执行
s.parentNode.removeChild(s); // 执行完就摘掉,别留在 DOM 里
}
function loadInto(url, title) {
if (titleEl()) { titleEl().textContent = title || '行程'; }
if (body()) { body().innerHTML = '<p class="empty-hint">正在载入…</p>'; }
@@ -535,8 +580,12 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
.then(function (html) {
if (!body()) { return; }
body().innerHTML = html;
/* ① 先执行片段自带的内联脚本(它会定义/调用自己的初始化) */
runInlineScripts(body(), html);
/* ② 再补跑宿主这侧的通用升级(日期/antd/行程表单),按依赖顺序 */
if (window.fbUpgradeRange) { window.fbUpgradeRange(); }
if (window.fbBootAntd) { window.fbBootAntd(); }
if (window.fbNodeForm) { window.fbNodeForm(body()); }
var f = document.getElementById('nf-title');
if (f) { f.focus(); }
})
@@ -593,8 +642,10 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
return r.json().then(function (j) {
if (body() && j && j.modal) {
body().innerHTML = j.modal;
runInlineScripts(body(), j.modal); // 片段里的内联脚本要执行(见 loadInto 的长注释)
if (window.fbUpgradeRange) { window.fbUpgradeRange(); }
if (window.fbBootAntd) { window.fbBootAntd(); }
if (window.fbNodeForm) { window.fbNodeForm(body()); }
}
});
}