diff --git a/assets/js/cal_sync.js b/assets/js/cal_sync.js new file mode 100644 index 0000000..06d298d --- /dev/null +++ b/assets/js/cal_sync.js @@ -0,0 +1,338 @@ +/* ============================================================================ + 日程「就地更新」—— 新建 / 修改**不刷新页面**(工单 #37) + ---------------------------------------------------------------------------- + 用户原话(2026-11-01): + 「日程又有了新bug,修改一个日程时,页面会刷新,这样如果我新建或者修改的日程 + 不是今天的,那么当刷新后,就看不到我编辑的日程的变化,不知道修改成功了没有。 + 修改和新建,都不应该刷新页面,而是使用js动态调整。」 + + ---------------------------------------------------------------- 为什么这么写 + 历史上「新建不刷新」是靠**手写复刻服务端渲染**做的(`templates/calendar/index.php` + 底部那段 `putMonth` / `putTimed` / `relayoutColumn`)。那段代码自己的注释里就写着: + · "插入用的定位方式必须与服务端渲染的完全一致,否则刷新一次小条就会跳位置" + · 一次真实事故:新加一条 07:00 的日程被 append 到末尾 ⇒ 落进「+N 条」看不见, + 可刷新一次又跑到第一位 + 修改要处理的情况比新建多得多(跨天变了 / 全天状态变了 / 换了日历本改了颜色 / + 改了重复规则 / 从这天挪到了别月),复刻必然出错。 + + ⇒ 本文件改用**服务端渲染片段 → 前端整块替换**: + 改完 → 取一份服务端画好的视图 → 换掉容器里的内容。 + 只有一处渲染,"插进去的样子"与"刷新一次的样子"**不可能**不一致。 + + ---------------------------------------------------------------- 三个落点 + ① `[data-cal-view]` 日历页的月/周/日视图(`templates/calendar/_view.php`) + ② `#homeCard-agenda` 首页「今天的日程」卡片(`templates/home/_card_agenda.php`) + ③ 「+N 条」清单的 `#calDayData` 数据岛(月视图那一份 JSON,换掉后清单才跟着变对) + + ---------------------------------------------------------------- 硬约束(都是踩过的) + · **只换内容、不换容器**:容器上挂着 `data-cal-view-kind` 之类的上下文, + 换掉会让 CSS 的 `.cal-main > *` 重算(视觉会跳一下),而且监听会丢。 + · **替换前记滚动位置**:`.hc-agenda [data-ag-scroll]` 进场会滚到"现在", + 不恢复的话用户正看着的那条就被弹走了。 + · **替换后要补跑三件事**:红线定时器(`data-now-line` 是 app.js 首屏扫的)、 + 「+N 条」数据岛、以及本文件自己的重绑。否则换进来的新节点是"死的"。 + · **幂等**:脚本全站加载,被重复引入(AJAX 换正文、模板重复)不能绑两遍 —— + `window.__fbCalSyncBound` 闸门。 + · **拿不到片段就退回整页刷新**:宁可刷一次,不要"没刷新却也没变"。 + 用户最怕的就是"不知道修改成功了没有",那种状态比刷新更糟。 + ============================================================================ */ +(function () { + 'use strict'; + if (window.__fbCalSyncBound) { return; } + window.__fbCalSyncBound = true; + + /* ---------------------------------------------------------------- 工具 */ + + function byId(id) { return document.getElementById(id); } + + /** 当前页面是哪个视图:从容器上读(服务端渲染时就写好了)。 */ + function viewKind() { + var el = document.querySelector('[data-cal-view]'); + return el ? (el.getAttribute('data-cal-view-kind') || '') : ''; + } + + /** + * 拼局部重绘要请求的地址。 + * + * ⚠️ 参数**照抄当前页面的 URL**:用户在月视图看 11 月,就还取 11 月; + * 在周视图看某周,就还取那一周。绝不"按改动的那条日程重新算" —— + * 改一条别的月份的日程时,那会把用户正在看的那一屏换掉。 + * 这条是用户报的现象的核心(改完刷新,跳到了别的月份/别的视图)。 + */ + function viewUrl() { + var kind = viewKind(); + if (!kind) { return ''; } + var q = new URLSearchParams(); + q.set('r', 'calendar/view'); + q.set('view', kind); + /* 上下文全部从**容器自己身上**读(服务端渲染时写的,准)。 + 不去猜隐藏域、也不从页头翻页链接里抠字符串 —— 那些在"翻过页"之后会对不上 + (踩过:`#calJumpD` 带的是 anchor,翻到 12 月它还是 11 月那天的日期)。 */ + var el = document.querySelector('[data-cal-view]'); + var ym = el ? (el.getAttribute('data-cal-ym') || '') : ''; + var anc = el ? (el.getAttribute('data-cal-anchor') || '') : ''; + if (kind === 'week' || kind === 'day') { + if (anc) { q.set('d', anc); } + } else if (ym) { + q.set('ym', ym); + } + return 'index.php?' + q.toString(); + } + + function fetchHtml(url) { + return fetch(url, { + credentials: 'same-origin', + headers: { 'X-Requested-With': 'XMLHttpRequest' } + }).then(function (r) { + if (!r.ok) { throw new Error('http ' + r.status); } + return r.text(); + }); + } + + /* ---------------------------------------------------------------- 重绘 */ + + /** 记录 / 恢复首页日程卡片的滚动位置(它进场会滚到"现在")。 */ + function grabScroll() { + var box = document.querySelector('#homeCard-agenda [data-ag-scroll]'); + return box ? box.scrollTop : null; + } + function putScroll(top) { + if (top === null || top === undefined) { return; } + var box = document.querySelector('#homeCard-agenda [data-ag-scroll]'); + if (box) { box.scrollTop = top; } + } + + /** + * 把新片段塞进容器:**只换子节点,不动容器本身**。 + * + * 片段顶层是一个 `