用户:「网页端,首页,日程好像显示不全,右侧是缺一块的,是不是左侧有刻度, 导致你算宽度的时候,没算对?」—— 猜对了,而且比想的严重。 原因:色块内联 `left: calc(46px + L%)` 是**绝对**让位(46px 给刻度), `width: calc(W% - 4px)` 的百分比却相对**含刻度的整宽**(.ag-grid 481px)。 于是 100% 时色块宽 477、左端 48,右端落到 744,而卡片右边界只有 700 ⇒ 多出 44px 被 .hc-agenda 的 overflow:hidden 裁掉,日历本名正好在那截里。 日历页 .tg-block 没这个病:它左边刻度是独立的一列(.tg-col),百分比天然相对列宽。 修法(换参照系,不是调数字):新增 .ag-lanes 定位层,左让刻度、右留 2px, 色块与红线都挪进去,百分比就相对真正可用的宽度算;刻度宽度收成单一变量 --ag-gutter(50px),以后改刻度只改它。内联删掉 46px。 验证四层: - _fb_agenda_geom.py 27/0:不越界 / 左右留白 2&4px / 宽度自洽 / 与日历页同公式 - _fb_agenda_pixel.py 8/0:截图数着色列,右沿留白 5px(修前 0px) - _fb_agenda_falsify.py 11/0:退回旧写法与车道超宽都变红,合法微调仍绿 - e2e 1558/0 · 几何 428/0 · b37 5/0 同时修两处失效探针: - _fb_b37_ui.py 点 #taskAddBtn(v78 已改成 [data-tk-add-open])导致基线超时全红 - _fb_b37_falsify.py 注入锚点写死 calc(46px + ...),改到会重现该 bug 的 width 上 部署脚本加 v80 三条静态断言(已逐条注入确认会红)。
206 lines
11 KiB
PHP
206 lines
11 KiB
PHP
<?php
|
||
/**
|
||
* 首页卡片 · 今天的日程(v62 从 templates/home.php 拆出来的,逻辑一字未改)。
|
||
*
|
||
* 用户原话(v24 第 24 批 第 3 条):「日程就显示今天的日程,也就是日视图那样形式,
|
||
* 为了好看高度需要固定。这样的话,鼠标放上去之后,向下可以滚动,暂时不要出现
|
||
* 滚动条太丑了。为了节省上下滚动的这个空间,我们可以把没有日程的那些时间
|
||
* 掐头去尾给它去掉。…需要在当前的时间或者是时刻那里显示一条红线。」
|
||
*
|
||
* ⚠️ 高度现在由**卡片高度**决定(`.hc-h*`),不再写死 440px —— 这正是"卡片化"
|
||
* 的核心:同一份内容,用户让它多高它就多高。
|
||
*
|
||
* @var array $todayEvents 今天的事件(全天在前、其余按时刻升序)
|
||
*/
|
||
$allDay = [];
|
||
$timed = [];
|
||
foreach (($todayEvents ?? []) as $ev) {
|
||
if ((string)($ev['time'] ?? '') === '') {
|
||
$allDay[] = $ev;
|
||
} else {
|
||
$timed[] = $ev;
|
||
}
|
||
}
|
||
|
||
// ---- 掐头去尾:只画"第一个日程前一小时"到"最后一个日程后一小时" ----
|
||
$startH = 0;
|
||
$endH = 24;
|
||
if ($timed) {
|
||
$mins = array_map(static function (array $e): int {
|
||
// ⚠️ 变量名带 `ag` 前缀:这是**被 require 进来的片段**,裸赋值 $p 会撞上
|
||
// 宿主模板里同名的 $p(流水线的"片段不许覆盖宿主变量"检查会拦下来)。
|
||
$agHM = explode(':', (string)$e['time']);
|
||
return ((int)($agHM[0] ?? 0)) * 60 + (int)($agHM[1] ?? 0);
|
||
}, $timed);
|
||
$startH = max(0, intdiv(min($mins), 60) - 1);
|
||
$endH = min(24, intdiv(max($mins), 60) + 2);
|
||
/* 🔴 v28 用户第 1 条:「今天的日程,并没有定位到红线所在的位置」。
|
||
根因:**区间里没有"现在"**,于是红线不画、定位算出来是越界值。
|
||
⇒ 让区间永远包含当前时刻(用户要的正是"一眼看到现在处在哪儿")。
|
||
🔴🔴 v44 第 3 条:首页红线"停在整点"的真根因是这里以前用 `date('G')`
|
||
(只取小时),而日程页用的是 `小时*60+分钟`。现在两处同一口径。 */
|
||
$nowHNow = (int)date('G');
|
||
if ($nowHNow < $startH) {
|
||
$startH = max(0, $nowHNow - 1);
|
||
}
|
||
if ($nowHNow >= $endH) {
|
||
$endH = min(24, $nowHNow + 2);
|
||
}
|
||
if ($endH <= $startH) {
|
||
$endH = min(24, $startH + 2);
|
||
}
|
||
}
|
||
$nowMinutes = ((int)date('G')) * 60 + (int)date('i');
|
||
$nowPos = (($nowMinutes / 60) - $startH) / max(1, ($endH - $startH));
|
||
$showNow = $nowPos >= 0 && $nowPos <= 1;
|
||
|
||
/* 冲突分列(v30 第 2/4 条):与日程页同一套算法 —— 两条撞车就左右各半。 */
|
||
usort($timed, static function (array $a, array $b): int {
|
||
return strcmp((string)($a['time'] ?? ''), (string)($b['time'] ?? ''));
|
||
});
|
||
$laneInfo = \App\Calendar::conflictLanes($timed);
|
||
foreach ($timed as $i => &$evRef) {
|
||
[$l, $w] = $laneInfo[$i] ?? [0.0, 1.0];
|
||
$evRef['left'] = round($l * 100, 4);
|
||
$evRef['width'] = round($w * 100, 4);
|
||
}
|
||
unset($evRef);
|
||
?>
|
||
<div class="hc-body hc-agenda">
|
||
<?php if (!$todayEvents): ?>
|
||
<div class="hd-pane-body">
|
||
<p class="empty-hint">今天没有安排。</p>
|
||
</div>
|
||
<?php else: ?>
|
||
<?php /* 全天日程**不在滚动容器里**(v24 第 26 批 第 2 条:要"一直显示在上面")。
|
||
为什么不用 sticky:sticky 只能粘在滚动区顶端,滚起来仍会被"标题+滚动区"
|
||
两层挤着,看起来还是在动;直接移出滚动容器是零副作用的做法。 */ ?>
|
||
<?php if ($allDay): ?>
|
||
<div class="ag-allday">
|
||
<?php foreach ($allDay as $ev): ?>
|
||
<?php
|
||
$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]); }
|
||
?>
|
||
<a class="ag-allday-item" href="<?= e((string)$ev['href']) ?>"
|
||
data-fb-entry='<?= fb_entry_json($ev, (string)($ev['href'] ?? ''), $evEdit) ?>'>
|
||
<span class="dash-dot" style="background:<?= e((string)$ev['color']) ?>;"></span>
|
||
<span class="ag-ad-t">全天</span>
|
||
<span class="ag-ad-w"><?= e((string)$ev['title']) ?></span>
|
||
<span class="dash-cal" style="background:<?= e((string)$ev['color']) ?>;"><?= e((string)$ev['calname']) ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
<div class="hd-pane-body" data-ag-scroll>
|
||
<div class="ag-grid">
|
||
<?php for ($h = $startH; $h < $endH; $h++): ?>
|
||
<div class="ag-row">
|
||
<span class="ag-hour"><?= sprintf('%02d:00', $h) ?></span>
|
||
<span class="ag-line"></span>
|
||
</div>
|
||
<?php endfor; ?>
|
||
<?php /* 🔴🔴 v80(用户 2026-10-22):「首页日程右侧是缺一块的,是不是左侧有刻度,
|
||
导致你算宽度的时候,没算对?」—— 用户猜对了。
|
||
⇒ 色块全部挪进这个 `.ag-lanes` 层:它左边界让出刻度(`--ag-gutter`)、
|
||
右边留 2px,于是下面那些 `left: X%` / `width: calc(Y% - 4px)`
|
||
的百分比相对的是**真正可用的宽度**。
|
||
⚠️ 之前没有这一层,百分比按"含刻度的整宽"算,色块右端比卡片**多出 44px**
|
||
被 `overflow:hidden` 裁掉 —— 那就是"缺一块"。 */
|
||
?>
|
||
<div class="ag-lanes">
|
||
<?php if ($showNow): ?>
|
||
<?php /* `data-now-min` 是给探针的零容差观测点(服务端用的分钟数),
|
||
不影响渲染 —— 见 v45 那条注释。 */ ?>
|
||
<div class="ag-now" style="top: <?= number_format($nowPos * 100, 2) ?>%"
|
||
data-now-min="<?= (int)$nowMinutes ?>"></div>
|
||
<?php endif; ?>
|
||
<?php
|
||
/* 日程块:按"相对第一个刻度的小时数"定位。
|
||
⚠️ v80 起 `left` / `width` 的百分比**只相对车道宽**(`.ag-lanes`),
|
||
不再自己加 46px —— 加过一次就是"两套账混算"(见上面那段注释)。
|
||
`- 4px` 是左右各 2px 的视觉留白,与日历页 `.tg-block` 的 `- 4px` 同款。 */
|
||
foreach ($timed as $ev):
|
||
$agHM = explode(':', (string)$ev['time']);
|
||
$evMin = ((int)($agHM[0] ?? 0)) * 60 + ((int)($agHM[1] ?? 0));
|
||
$topPct = (($evMin / 60) - $startH) / max(1, ($endH - $startH)) * 100;
|
||
$topPct = max(0, min(96, $topPct));
|
||
$et2 = (string)($ev['end_time'] ?? '');
|
||
if ($et2 !== '' && preg_match('/^(\d{1,2}):(\d{2})/', $et2, $mm2)) {
|
||
$endMin = ((int)$mm2[1]) * 60 + ((int)$mm2[2]);
|
||
} else {
|
||
$endMin = $evMin + 30;
|
||
}
|
||
if ($endMin <= $evMin) { $endMin = $evMin + 30; }
|
||
$endMin = min($endMin, $endH * 60);
|
||
$hPct = ($endMin / 60 - $startH) / max(1, ($endH - $startH)) * 100 - $topPct;
|
||
$hPct = max(2.2, $hPct);
|
||
$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]); }
|
||
?>
|
||
<a class="ag-ev" href="<?= e((string)$ev['href']) ?>"
|
||
data-fb-entry='<?= fb_entry_json($ev, (string)($ev['href'] ?? ''), $evEdit) ?>'
|
||
style="background: <?= e((string)$ev['color']) ?>;
|
||
top: <?= number_format($topPct, 2) ?>%;
|
||
height: <?= number_format($hPct, 2) ?>%;
|
||
left: calc(<?= (float)($ev['left'] ?? 0) ?>% + 2px);
|
||
width: calc(<?= (float)($ev['width'] ?? 100) ?>% - 4px);"
|
||
title="<?= e((string)$ev['title']) ?>"
|
||
data-cal="<?= (int)$ev['cal'] ?>">
|
||
<?php /* 🔴 v63(用户 2026-10-19 第 2 条):「首页日程显示,把所属的日历本,
|
||
放在色块的右侧。」⇒ 时间与标题包进 `.ag-ev-main`(占据剩余宽度、可收缩),
|
||
日历本作为它右边的**兄弟**。
|
||
⚠️ 原来是「时间 float:left + 标题 block + 日历本 block」三板斧:日历本
|
||
`display:block` 必然被顶到第二行,色块一矮就完全看不见。改成弹性行
|
||
后,日历本与标题同一行、永远在右。 */ ?>
|
||
<span class="ag-ev-main">
|
||
<span class="ag-ev-t"><?= e((string)$ev['time']) ?></span>
|
||
<span class="ag-ev-w"><?= e((string)$ev['title']) ?></span>
|
||
</span>
|
||
<span class="ag-ev-cal"><?= e((string)$ev['calname']) ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php if ($todayEvents): ?>
|
||
<script>
|
||
/* 进场定位到"现在"(v24 第 26 批 第 2 条):让红线落在可视区**正中**。
|
||
⚠️ 用 scrollTop 直接赋值,**不要** scrollIntoView({block:'center'}) ——
|
||
后者会把整个浏览器窗口也滚下去,一进首页就被拽到页面中部。
|
||
⚠️ 只判"有没有网格可滚",别判"有没有分时段日程"(v29 踩过:只有全天日程时
|
||
脚本整段不输出,而网格和红线照样渲染 ⇒ 红线在、页面停在最上面)。 */
|
||
(function () {
|
||
var box = document.querySelector('#homeCard-agenda [data-ag-scroll]')
|
||
|| document.querySelector('[data-ag-scroll]');
|
||
if (!box) { return; }
|
||
var line = box.querySelector('.ag-now');
|
||
var RATIO = <?= json_encode(round($nowPos, 6)) ?>;
|
||
function targetOffset() {
|
||
if (line) { return line.offsetTop; }
|
||
var grid = box.querySelector('.ag-grid');
|
||
return grid ? grid.offsetHeight * RATIO : 0;
|
||
}
|
||
function centerOnNow() {
|
||
box.scrollTop = Math.max(0, targetOffset() - (box.clientHeight / 2));
|
||
}
|
||
centerOnNow();
|
||
if (window.requestAnimationFrame) {
|
||
requestAnimationFrame(function () { requestAnimationFrame(centerOnNow); });
|
||
}
|
||
})();
|
||
</script>
|
||
<?php endif; ?>
|