\App\Calendar::canWrite($c, (int)\App\Auth::personId()))); $visCals = array_map('intval', (array)($visCals ?? [])); $hiddenCals = array_map('intval', (array)($hiddenCals ?? [])); $newAt = (string)($newAt ?? ''); // 能不能"点一下建日程":只读账号点了没反应,所以连 rt-writable 标记都不给。 // 🔴 v48(用户 2026-10-14 第 3 条):**十字光标已去掉**(CSS 里不再有 cursor:crosshair), // 但 `rt-writable` 这个**类名保留** —— 它是"这个人能不能写"在 DOM 上的标记位, // 点击建日程的判断逻辑与此无关(见底部 JS 的 $canWrite && $writable)。 $canNew = $canWrite && (bool)$writable; // 三种看法各自的「上一段 / 下一段」:月按整月走,周按 7 天走,天按 1 天走 $step = $view === 'week' ? 7 : ($view === 'day' ? 1 : 0); $prevD = date('Y-m-d', strtotime('-' . $step . ' days', strtotime($anchor))); $nextD = date('Y-m-d', strtotime('+' . $step . ' days', strtotime($anchor))); $todayD = date('Y-m-d'); $headText = $view === 'month' ? date('Y 年 n 月', $ts) : ($view === 'week' ? date('Y 年 n 月 j 日', strtotime($anchor)) . ' 起这一周' : date('Y 年 n 月 j 日', strtotime($anchor))); ?>

日程

月 周 天 上一月 今天 下一月 今天 而不是再引一个弹层:它的原生日历足够用, 而且**选中就有值**(change 事件)→ 直接跳转,不用再点一次"确定"。 桌面/手机/无 JS 三种情况下的行为都是可预期的。 */ ?>
` 在部分旧 WebView 上行为不一致, 而且我们已有的浮层已经解决了"点遮罩关、Esc 关、滚动锁"这几件事。 ⚠️ 表单本身**一个字没动**(字段名/服务端契约/CSRF 全照旧), 只把它的容器从"页面上的一格"换成"浮层里的一块"。 `calendar_id` 那个 select 也保留(第 7 条要求"选择日历本,带上颜色", 见下面给每个 option 加的色块)。 */ ?> 日历本` / `$evMvStyle = true` —— 现在这些串在新片段里。 不改的话:正向断言当场中止部署,负向断言恒真没牙齿(v78 原话)。 */ ?> $ym, 'view' => $view, 'anchor' => $anchor, 'new_at' => $newAt, // 模块页只管自己:'' = 提交完回日程模块(不写 'home') 'back' => '', ]; require __DIR__ . '/_add_modal.php'; /* 🔴🔴 v82:**只清掉三个"调用参数"**,别的什么都不许 unset。 为什么:`require` 与宿主**同一作用域**,片段里赋的每个变量都会留下来。 我第一版顺手把 `$ym/$view/$anchor/$newAt/$writable` 也 unset 了 —— 而这五个正是**模块页自己后文还要用的**(页头标题、翻页链接、周/日网格、 `.tg-now` 的 top 计算全依赖它们)。unset 掉不会报错,只会让后半页 静默变成空值:页头显示"1970 年"、翻页链接丢参数、时间网格画不出来。 这类"清了别人的变量"比"片段拿不到变量"更难查 —— 页面能开,只是全错。 ⇒ 判据:unset 之前先 grep 一下后文还用不用它(本项目吃过"片段覆盖宿主变量"的亏)。 🔴 v82 再修(同日):片段内部现在已经**改用 `$cal*` 前缀**,不再覆盖 宿主的 `$anchor/$newAt/$writable/$view/$ym`(见该片段头部那段说明)。 所以这里连"片段残留物"都不必再清 —— 真正要清的只有本模板自己传进去的 三个 `$cal*` 入参。 */ unset($calCanNew, $calWritable, $calCtx); ?>
全天
$c): ?>
0) { $evEdit = url('calendar/event', ['id' => $evRef, 'embed' => 1]); } elseif ($evKind === 'movie' && $evRef > 0) { $evEdit = url('movies/edit', ['id' => $evRef, 'embed' => 1]); } elseif ($evKind === 'task' && $evRef > 0) { $evEdit = url('tasks/edit', ['id' => $evRef]); } elseif ($evKind === 'anniv' && $evRef > 0) { $evEdit = url('days/edit', ['id' => $evRef, 'embed' => 1]); } ?> ' style="background: ;" href="" title="">
$c): ?>
0) { $evEdit = url('calendar/event', ['id' => $evRef, 'embed' => 1]); } elseif ($evKind === 'movie' && $evRef > 0) { $evEdit = url('movies/edit', ['id' => $evRef, 'embed' => 1]); } elseif ($evKind === 'task' && $evRef > 0) { $evEdit = url('tasks/edit', ['id' => $evRef]); } // trip 的条目指向"某个旅程里的某段行程",编辑器在旅程详情页里 —— 这里不给「编辑」 elseif ($evKind === 'anniv' && $evRef > 0) { $evEdit = url('days/edit', ['id' => $evRef, 'embed' => 1]); } ?> data-fb-entry='' style="background: ; top: %; height: %; left: calc(2px + %); width: calc(% - 4px);" href="" title="">
` 而不是把 JSON 塞进 data-*: data-* 属性里几千条会被 HTML 转义得又长又容易截断; JSON 里若出现 `` 也会截断 —— 所以下面统一走 json_encode 的 JSON_HEX_TAG(把 < > 转成 \u003C \u003E,永远不会提前闭合)。 ⚠️ 这是**渲染层**的便利数据,字段与 `data-fb-entry` 同一套(fb_entry_json), 所以浮层能沿用全部既有逻辑(详情/编辑/删除/来源判断)。 */ $dayData = []; foreach (($weeks ?? []) as $wk) { foreach ($wk as $cell) { if (!$cell['events']) { continue; } $items = []; foreach ($cell['events'] as $ev) { $evKind = (string)($ev['kind'] ?? 'custom'); $evRef = (int)($ev['ref_id'] ?? 0); $evEdit = ''; if ($evKind === 'custom' && $evRef > 0) { $evEdit = url('calendar/event', ['id' => $evRef, 'embed' => 1]); } elseif ($evKind === 'movie' && $evRef > 0) { $evEdit = url('movies/edit', ['id' => $evRef, 'embed' => 1]); } elseif ($evKind === 'task' && $evRef > 0) { $evEdit = url('tasks/edit', ['id' => $evRef]); } elseif ($evKind === 'anniv' && $evRef > 0) { $evEdit = url('days/edit', ['id' => $evRef, 'embed' => 1]); } $items[] = ['_rank' => \App\Calendar::displayRank($ev), '_time' => (string)($ev['time'] ?? '')] + json_decode( fb_entry_json($ev, (string)($ev['href'] ?? ''), $evEdit), true ); } $dayData[(string)$cell['date']] = $items; } } ?>
自己处理,JS 会排除掉)。 */ ?>
,点不开 = 完全看不到。 两处都已修:① `Calendar::displayRank()` 让派生条目排最后; ② 下面把 "+N 条" 换成**真按钮**,点开当天的全部条目浮层。 ⚠️ 保留 3 条不改成"全画":格子是固定高度(min-height:104px), 15 条硬塞会把月网格撑得不像月历;超出部分交给浮层看。 */ $cellEvents = $cell['events']; $shown = 3; $hiddenCnt = count($cellEvents) - $shown; ?> 0) { $evEdit = url('calendar/event', ['id' => $evRef, 'embed' => 1]); } elseif ($evKind === 'movie' && $evRef > 0) { $evEdit = url('movies/edit', ['id' => $evRef, 'embed' => 1]); } elseif ($evKind === 'task' && $evRef > 0) { $evEdit = url('tasks/edit', ['id' => $evRef]); } elseif ($evKind === 'anniv' && $evRef > 0) { $evEdit = url('days/edit', ['id' => $evRef, 'embed' => 1]); } ?> ' style="background: ;" href="" title=""> 0): ?> ` 兜住无 JS 的情况:直接跳那天的日视图 (日视图按时间网格铺开,不会再截断)。 */ ?>
** ⇒ 被折叠的条目**永远看不到**。 这里给那颗按钮一个真正的去处。 ⚠️ 为什么不直接复用条目详情浮层(`fbEntryModal`): 那是"**一条**日程的详情/编辑/删除",一次只画一条; 这里要的是"**当天的清单**",点其中任意一条才进那条的详情。 两者叠用是对的:先出清单,点清单里某条 → 调 `window.fbEntryOpen()` 进详情层。 ⚠️ 详情层是 `position:fixed` 的高层,清单层在它下面 —— 关掉详情层会**回到清单**, 符合"我从当天的清单里点进来的"这个来路(不用再点一次 +N 条)。 ⚠️ 数据直接读上面那个 `