跨天日程:周/日视图按列分段画成一条,不再拆成碎片 laoyang/FamilyBook#35
用户 2026-10-30:「网页端,跨天日程显示还是有问题…如果一个日程跨了三天,
应该在日历本上怎么显示呢?在周视图和日视图,应该怎么显示?现在显示的有问题。」
## 修复前是什么样(实测,不是推断)
一个「11/10 20:00 → 11/12 02:00」的跨三天日程,服务端 month() 展开成三条
(首日带 time、中间天两者皆空、末日带 end_time),喂给 build_time_grid 后:
全天行:11/11、11/12 各一条 ← 中间两天变成"全天日程"
时间网格:11/10 一个小块,高度仅 2.08%(≈30 分钟)
三个问题叠加:
· 开始日的结束时刻不在同一天 ⇒ 判成"没有结束" ⇒ 退化成 30 分钟
· 中间/末日既无 time 又不是全天 ⇒ 被"按全天处理"的兜底塞进全天行
· 三条互不相干,用户看不出"这是同一个日程"
## 方案(用户已确认)
1. 周视图:跨列连续长条 —— 毎列各画一段,视觉连成一条
2. 日视图:铺满当天并标延续
3. 中间天归时间网格,不进全天行
## 改了什么
**app/Calendar.php** —— 服务端补两个字段
· `span_days`:整条日程跨了几天(0 = 当天来回)
· `seg`:start / mid / end / only
同时修掉一个真 bug:原来 `$first` 是"窗口内遍历的第一个",
日视图只查一天时,中间天会变成 `$first` 被当开始日处理。
⇒ 一律用**真实日期**跟 `$base`/`$base+span` 比较,不看遍历顺序。
**index.php::build_time_grid** —— 核心重构
· 先按 `kind:ref_id:occ` 归堆,把同一日程的逐天条目合成一条
(带 occ 是因为重复日程不同次发生的 ref_id 相同;带 kind 是因为
观影/旅程的 ref_id 是各自表里的 id,`custom:5` 和 `movie:5` 是两回事)
· 跨度以服务端 `span_days` 为准,不再用"窗口内铺了几列"推断
(日视图下那个推断必然不成立)
· 跨天条目拆成每列一段,按 `seg` 决定纵坐标:
start = 开始时刻→24:00,mid = 整列,end = 0→结束时刻
· 跨天条目**不参与 conflictLanes** —— 它必然占满整列,
拿去分列会把同一天所有日程都挤窄
**templates/calendar/index.php** —— 分段渲染
· `is-seg-start/mid/end` 类;延续段前面加 `↳` 标记,不重复显示时间
· 跨天段用 `left:0; width:100%`,单天块保持原来的 `calc(2px + N%)`
**assets/css/app.css** —— 让三段看着连成一条
· start 下圆角归零、下边框透明;mid 上下都平;end 上圆角归零、上边框透明
· 段与段之间原本有 1px 白边 + 左右内缩,实测缝隙 10.9px,看着像断成三段
⇒ 跨天段贴满整列后降到 1.0px(只剩列边框)
## 为什么在列内画段,不做跨列绝对定位层
`.tg-body` 左边有 56px 固定 gutter,窄屏还会横向滚动
(`.cal-tg-card{overflow:auto}` + 每列 min-width 92px),`.tg-col` 又是
`overflow:hidden`。跨列层要自己算 gutter 偏移、跟着横向滚动跑、还不能被裁 ——
每个都是新坑。在列内画段天然对齐,代价只是段间有 1px 列边框缝,用 CSS 糊掉。
## 验证
**逻辑层(PHP,用真实 build_time_grid)**
· 周视图跨三天:3 段(start/mid/end),全天行为空 ✓
· 日视图三个位置(起始/中间/结束天)各自正确 ✓
· 回归:单天 + 真全天仍正常;冲突分列仍生效;跨天与普通日程共存正常 ✓
· 反向验证:用修复前的代码跑同一判据**正确转红**
(旧版 timed=1 all_day=2,高度只有 2.08%),证明判据非恒真 ✓
**视觉层(Playwright + 真实 CSS + include 真实模板)32 项全过**
· 横向连续:start→mid 缝隙 1.0px,mid→end 1.0px(判据 ≤2.5px)✓
· 纵向:start 底边贴列底 0px;mid 占满整列(顶 0 / 底 0);
end 顶边贴列顶 0px 且只占开头一小段 ✓
· 样式:start 下圆角 0px + 下边框透明;mid 上下圆角 0;end 上圆角 0 +
上边框透明 ✓
· 无裁切:三段都落在列内 ✓
· 回归:真全天仍在全天行;跨天不再出现在全天行;单天日程正常 ✓
· 窄屏 375px:三段仍可见(w=91)✓
**反向验证 4/4 全部抓到**
故意做坏四个变体(段留单天内缩 / 保留圆角 / 中间段只画 30% /
下边框不透明),判据全部转红 —— 说明"32 项全过"不是因为判据恒真。
## 已知取舍
跨天段占满整列,同一天的普通日程与它在视觉上会重叠。
这是日历的固有取舍(Google 日历同样如此),用户确认的方案里也接受。
This commit is contained in:
@@ -296,16 +296,49 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
|
||||
$evE = $mE ? ((int)$mE[1] * 60 + (int)$mE[2]) : null;
|
||||
if ($evE !== null && $evE <= $evS) { $evE = null; }
|
||||
?>
|
||||
<a class="tg-block" data-cal="<?= (int)$ev['cal'] ?>"
|
||||
<?php
|
||||
/* 🔴 v88(工单 #35):跨天日程的分段。
|
||||
`seg` 由 `build_time_grid` 给出:
|
||||
start = 起始段(从开始时刻到当日 24:00)
|
||||
mid = 中间整天
|
||||
end = 结束段(00:00 到结束时刻)
|
||||
only / 空 = 单天日程(原样)
|
||||
视觉上要让三段"连成一条":把段与段之间的圆角和内侧边框去掉,
|
||||
只保留最外两端是圆的。具体在 CSS 的 `.tg-block.is-seg-*` 里。 */
|
||||
$evSeg = (string)($ev['seg'] ?? '');
|
||||
$evCont = !empty($ev['cont']);
|
||||
$segCls = '';
|
||||
if ($evSeg === 'start') { $segCls = ' is-seg-start'; }
|
||||
elseif ($evSeg === 'mid') { $segCls = ' is-seg-mid'; }
|
||||
elseif ($evSeg === 'end') { $segCls = ' is-seg-end'; }
|
||||
/* 分隔线:起始段的下边不画(要跟下一段接上),
|
||||
中间段上下都不画,结束段上边不画 —— 由 CSS 处理。 */
|
||||
?>
|
||||
<a class="tg-block<?= $segCls ?>" data-cal="<?= (int)$ev['cal'] ?>"
|
||||
data-s="<?= (int)$evS ?>"<?= $evE !== null ? ' data-e="' . (int)$evE . '"' : '' ?>
|
||||
<?php if ($evSeg !== '') : ?>data-seg="<?= e($evSeg) ?>"<?php endif; ?>
|
||||
data-fb-entry='<?= fb_entry_json($ev, $evDet, $evEdit) ?>'
|
||||
<?php
|
||||
/* 🔴 v88:跨天段用**贴满整列**的定位(left:0 / width:100%),
|
||||
和单天块的 `calc(2px + N%)` / `calc(N% - 4px)` 不是一套。
|
||||
单天块要留 2px 好跟列边框区分;跨天段要跟上一列接上,
|
||||
留缝就"断成三段"了(实测 10.9px 空隙)。
|
||||
⚠️ 用 CSS 类而不是内联值区分:内联 calc 里做条件太绕,
|
||||
而且两边混着写以后必然有人改错一边。 */
|
||||
$evSegStyle = ($evSeg !== '')
|
||||
? 'left: 0; width: 100%;'
|
||||
: 'left: calc(2px + ' . (float)($ev['left'] ?? 0) . '%); '
|
||||
. 'width: calc(' . (float)($ev['width'] ?? 100) . '% - 4px);';
|
||||
?>
|
||||
style="background: <?= e((string)$ev['color']) ?>;
|
||||
top: <?= (float)$ev['top'] ?>%; height: <?= (float)$ev['height'] ?>%;
|
||||
left: calc(2px + <?= (float)($ev['left'] ?? 0) ?>%);
|
||||
width: calc(<?= (float)($ev['width'] ?? 100) ?>% - 4px);"
|
||||
<?= $evSegStyle ?>"
|
||||
href="<?= e((string)$ev['href']) ?>"
|
||||
title="<?= e((string)$ev['title']) ?>">
|
||||
<b><?= e((string)$ev['time']) ?></b> <?= e((string)$ev['title']) ?>
|
||||
title="<?= e((string)$ev['title']) ?><?= $evCont ? '(接前一日)' : '' ?>">
|
||||
<?php if (!$evCont && (string)($ev['time'] ?? '') !== '') : ?>
|
||||
<b><?= e((string)$ev['time']) ?></b>
|
||||
<?php endif; ?>
|
||||
<?php if ($evCont) : ?><span class="tg-cont">↳</span><?php endif; ?><?= e((string)$ev['title']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user