2026-10-19 12:51:17 +08:00
|
|
|
|
<?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 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
|
|
|
|
|
|
/* 日程块:按"相对第一个刻度的小时数"定位。
|
|
|
|
|
|
⚠️ left/width 与日历页 `.tg-block` 同一套(left = 46px 刻度 + 2px,
|
|
|
|
|
|
width = 列宽 - 4px)—— 曾经这里扣了 46px 的缝,导致两块之间空出 50px。 */
|
|
|
|
|
|
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(46px + <?= (float)($ev['left'] ?? 0) ?>% + 2px);
|
|
|
|
|
|
width: calc(<?= (float)($ev['width'] ?? 100) ?>% - 4px);"
|
|
|
|
|
|
title="<?= e((string)$ev['title']) ?>"
|
|
|
|
|
|
data-cal="<?= (int)$ev['cal'] ?>">
|
2026-10-19 17:27:37 +08:00
|
|
|
|
<?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>
|
2026-10-19 12:51:17 +08:00
|
|
|
|
<span class="ag-ev-cal"><?= e((string)$ev['calname']) ?></span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</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; ?>
|