v62:纪念日列表修正 / 分类图标与设置菜单 / 首页卡片体系
- 纪念日列表:display:flex 从 td 移到内层 div(写在 td 上会破坏表格列算法, 表现为关联人列与其它列高度不一致);行内列序按表头对齐; 改完再点「新建」不再带出上次修改的数据;文案改为「与我相关」「来自名册」; 别人记的、把我挂成关联人的条目一并显示(split() 返回 hiddenIds, 路由层据此过滤「接下来的日子」,与两张表同一套可见性) - 分类图标:新增 app/AnnivIcon.php(17 个行内 SVG 图标 + 同 key 的 emoji 供日历纯文本处使用,内网无 CDN 不用 FontAwesome);去掉「看日历」按钮; 设置按钮改为菜单,第一项进 days/settings 独立页;分类可自选图标, 列表徽标与日历条目都按分类显示 - 首页卡片:新增 app/HomeCards.php 与 templates/home_cards.php; 8 格宽 × 高 2/4/6/8;首页拆成 templates/home/_card_*.php 共 8 个片段; home/cards 可改尺寸、调顺序、开关
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
<?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'] ?>">
|
||||
<span class="ag-ev-t"><?= e((string)$ev['time']) ?></span>
|
||||
<span class="ag-ev-w"><?= e((string)$ev['title']) ?></span>
|
||||
<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; ?>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* 首页卡片 · 读书(v62 新增)。
|
||||
*
|
||||
* 数据来自 `HomeCards::booksData()`:**在读优先**,其余按新加进来的顺序排(最多 6 本)。
|
||||
*
|
||||
* @var array $cardData['books'] 每项含 id/title/author/status/progress/chapter_count/cover_key
|
||||
*/
|
||||
$bkRows = (array)($cardData['books'] ?? []);
|
||||
?>
|
||||
<div class="hc-body">
|
||||
<?php if (!$bkRows): ?>
|
||||
<p class="empty-hint">书架还是空的。</p>
|
||||
<?php else: ?>
|
||||
<ul class="hc-bk-list">
|
||||
<?php foreach ($bkRows as $b): ?>
|
||||
<?php
|
||||
$bkStatus = \App\Book::normalizeStatus((string)($b['status'] ?? 'wish'));
|
||||
$bkCh = (int)($b['chapter_count'] ?? 0);
|
||||
$bkPr = (int)($b['progress'] ?? 0);
|
||||
$bkPct = $bkCh > 0 ? min(100, (int)round($bkPr / $bkCh * 100)) : 0;
|
||||
?>
|
||||
<li class="hc-bk">
|
||||
<a class="hc-bk-cover" href="<?= e(url('books/show', ['id' => (int)$b['id']])) ?>">
|
||||
<?php if (!empty($b['cover_key'])): ?>
|
||||
<img src="<?= e(media_url((string)$b['cover_key'])) ?>" alt="" loading="lazy">
|
||||
<?php else: ?>
|
||||
<span class="hc-bk-ph" aria-hidden="true">📖</span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<div class="hc-bk-main">
|
||||
<a class="hc-bk-t" href="<?= e(url('books/show', ['id' => (int)$b['id']])) ?>"
|
||||
title="<?= e((string)$b['title']) ?>"><?= e((string)$b['title']) ?></a>
|
||||
<span class="small muted">
|
||||
<?= e((string)($b['author'] ?? '')) ?>
|
||||
<?php if ($bkCh > 0): ?>
|
||||
· <?= $bkPr ?>/<?= $bkCh ?> 章
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<span class="hc-bk-bar" aria-hidden="true"><i style="width: <?= (int)$bkPct ?>%"></i></span>
|
||||
</div>
|
||||
<span class="hc-bk-st is-<?= e($bkStatus) ?>"><?= e(\App\Book::statusWord($bkStatus)) ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* 首页卡片 · 概述(v62 从 templates/home.php 拆出来的)。
|
||||
*
|
||||
* 内容与 v24 第 24 批 用户第 2 条完全一致(问候 → 日期/星期/农历 → 日子标记 →
|
||||
* 今日数字 | 右侧彩色天气图标 + 温度/风力 + 地址),只换了外壳:
|
||||
* 以前是 `section.section > div.card.home-brief`,现在是卡片的 `.hc-body`。
|
||||
*
|
||||
* ⚠️ 这块**永远是横贯的**(默认 8 宽):它是"首页第一眼",被挤成一半宽就会折行折得很碎。
|
||||
* ⚠️ 用到的变量都由 home 路由传进来($hbPart 除外,这里自己按时段算)。
|
||||
*/
|
||||
$hbPart = '你好';
|
||||
$hbH = (int)date('G');
|
||||
if ($hbH < 5) { $hbPart = '夜深了'; }
|
||||
elseif ($hbH < 11) { $hbPart = '早上好'; }
|
||||
elseif ($hbH < 13) { $hbPart = '中午好'; }
|
||||
elseif ($hbH < 18) { $hbPart = '下午好'; }
|
||||
else { $hbPart = '晚上好'; }
|
||||
|
||||
$nDoing = 0;
|
||||
$nTodo = 0;
|
||||
foreach (($tasks ?? []) as $__t) {
|
||||
if ((string)($__t['status'] ?? 'todo') === 'doing') {
|
||||
$nDoing++;
|
||||
} else {
|
||||
$nTodo++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="hc-body home-brief">
|
||||
<div class="hb-left">
|
||||
<div class="hb-hello">
|
||||
<b><?= e($hbPart) ?></b><?php if (trim((string)($meName ?? '')) !== ''): ?>,<?= e((string)$meName) ?><?php endif; ?>
|
||||
</div>
|
||||
<div class="hb-date">
|
||||
<span class="hb-d"><?= e((string)($todayLabel ?? date('n 月 j 日'))) ?></span>
|
||||
<span class="hb-w"><?= e((string)($weekday ?? '')) ?></span>
|
||||
<?php if (($lunarShort ?? '') !== ''): ?>
|
||||
<span class="hb-lunar" title="<?= e((string)($lunarLine ?? '')) ?>"><?= e((string)$lunarShort) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if (!empty($todayMarks['tags'])): ?>
|
||||
<div class="hb-marks">
|
||||
<?php foreach ($todayMarks['tags'] as $mk): ?>
|
||||
<span class="hb-mark is-<?= e((string)$mk['kind']) ?>"><?= e((string)$mk['word']) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="hb-stats">
|
||||
<a class="hbs" href="<?= e(url('calendar')) ?>">
|
||||
<b><?= count($todayEvents ?? []) ?></b> 条日程(今天)
|
||||
</a>
|
||||
<?php /* 两个数字与右侧「近期任务」卡片**同源**(同一个 $tasks 数组),
|
||||
「在做」与「待办」按状态拆开数 —— 见 v53 第 2 条那条注释。 */ ?>
|
||||
<a class="hbs" href="<?= e(url('tasks')) ?>" title="近期任务(进行中)">
|
||||
<b><?= $nDoing ?></b> 件在做
|
||||
</a>
|
||||
<?php if ($nTodo > 0): ?>
|
||||
<a class="hbs" href="<?= e(url('tasks')) ?>" title="记下来了、还没动手的那些">
|
||||
<b><?= $nTodo ?></b> 件待办
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ((int)($stat7['overdue'] ?? 0) > 0): ?>
|
||||
<a class="hbs is-late" href="<?= e(url('tasks')) ?>">
|
||||
<b><?= (int)$stat7['overdue'] ?></b> 件逾期
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($weather)): ?>
|
||||
<div class="hb-weather is-<?= e((string)($weather['tone'] ?? 'cloud')) ?>">
|
||||
<span class="hbw-icon" aria-hidden="true"><?= e((string)($weather['icon'] ?? '')) ?></span>
|
||||
<div class="hbw-main">
|
||||
<div class="hbw-temp"><?= e((string)($weather['temp'] ?? '')) ?></div>
|
||||
<div class="hbw-text"><?= e((string)($weather['text'] ?? '')) ?></div>
|
||||
<?php if (($weather['wind'] ?? '') !== ''): ?>
|
||||
<div class="hbw-wind">
|
||||
<?= $weather['wdir'] !== '' ? e((string)$weather['wdir']) . ' ' : '' ?><?= e((string)$weather['wind']) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if (($weather['place'] ?? '') !== ''): ?>
|
||||
<div class="hbw-place"><?= e((string)$weather['place']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* 首页卡片 · 联系人(v62 新增)。
|
||||
*
|
||||
* 用户原话:「例如,联系人,可以有卡片,让用户选择显示几个联系人在首页上。」
|
||||
* ⇒ 张数由卡片设置里的「显示 N 个」决定(`HomeCards::contactsData($familyId,$me,$n)`);
|
||||
* 卡片高度不够时内部滚动 —— 不留一个"被截断但看不出来"的列表。
|
||||
*
|
||||
* ⚠️ 只列**我的名册**里的人(含别人分享给我的),不是全家所有人 —— 首页是"我的"。
|
||||
*
|
||||
* @var array $cardData['contacts'] ['rows'=>array,'total'=>int]
|
||||
*/
|
||||
$ctRows = (array)($cardData['contacts']['rows'] ?? []);
|
||||
$ctTotal = (int)($cardData['contacts']['total'] ?? 0);
|
||||
?>
|
||||
<div class="hc-body">
|
||||
<?php if (!$ctRows): ?>
|
||||
<p class="empty-hint">名册里还没有人。</p>
|
||||
<?php else: ?>
|
||||
<div class="hc-people">
|
||||
<?php foreach ($ctRows as $p): ?>
|
||||
<?php /* 头像 + 称呼(`person_chip` 自带底色与"我该怎么叫他")。
|
||||
⚠️ 这里用块级排列而不是一行一个 pill:卡片可能很窄(宽 2),
|
||||
pill 会挤成一列文字。 */ ?>
|
||||
<a class="hc-person" href="<?= e(url('people/show', ['id' => (int)$p['id']])) ?>"
|
||||
title="<?= e(person_name($p)) ?>">
|
||||
<?php if (!empty($p['avatar'])): ?>
|
||||
<img class="hc-person-av" src="<?= e(media_url((string)$p['avatar'])) ?>" alt="">
|
||||
<?php else: ?>
|
||||
<span class="hc-person-av hc-person-tx"
|
||||
style="background: <?= e(person_bg($p)) ?>; color: <?= e(person_fg($p)) ?>;">
|
||||
<?= e(mb_substr(person_name($p), 0, 1, 'UTF-8')) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<span class="hc-person-nm"><?= e(person_name($p)) ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if ($ctTotal > count($ctRows)): ?>
|
||||
<p class="small muted" style="margin:8px 0 0">共 <?= $ctTotal ?> 人,这里显示 <?= count($ctRows) ?> 位。</p>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* 首页卡片 · 纪念日(v62 新增)。
|
||||
*
|
||||
* 数据来自 `Anniversary::upcoming($familyId, $me, 60)`(与「纪念日」页面顶上那块**同一份**),
|
||||
* 所以"首页看到的"和"点进去看到的"永远一致 —— 不要在卡片里自己再算一遍农历。
|
||||
*
|
||||
* ⚠️ 图标按**分类**给(v62 第 2 条):走 `AnnivIcon::svg()`,颜色跟着文字色;
|
||||
* 与日历里的 emoji 是同一套 key(见 AnnivIcon 的类注释)。
|
||||
*
|
||||
* @var array $cardData['days'] 每项含 date/title/kind/icon/age/nth/days
|
||||
*/
|
||||
$annRows = (array)($cardData['days'] ?? []);
|
||||
?>
|
||||
<div class="hc-body">
|
||||
<?php if (!$annRows): ?>
|
||||
<p class="empty-hint">未来 60 天里没有纪念日。</p>
|
||||
<?php else: ?>
|
||||
<ul class="ann-up hc-ann">
|
||||
<?php foreach ($annRows as $a): ?>
|
||||
<li class="ann-up-item">
|
||||
<span class="ann-days"><?= (int)$a['days'] === 0 ? '今天' : ((int)$a['days'] . ' 天后') ?></span>
|
||||
<span class="ann-ic"><?= \App\AnnivIcon::svg((string)($a['icon'] ?? ''), 15) ?></span>
|
||||
<span class="ann-title"><?= e((string)$a['title']) ?></span>
|
||||
<?php if (($a['age'] ?? null) !== null): ?>
|
||||
<span class="small muted"><?= (int)$a['age'] ?> 岁</span>
|
||||
<?php elseif (($a['nth'] ?? null) !== null): ?>
|
||||
<span class="small muted">第 <?= (int)$a['nth'] ?> 个</span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* 首页卡片 · 往年的今天(v62 从 templates/home.php 拆出来的)。
|
||||
*
|
||||
* ⚠️ 只认「往年的今天」,**不退回同月**(v24 第 19 条)—— 见 home_memories() 的说明。
|
||||
* @var array $memories ['rows' => [['kind','years_ago','title','href','starts_at'], ...]]
|
||||
*/
|
||||
$mmRows = (array)($memories['rows'] ?? []);
|
||||
?>
|
||||
<div class="hc-body">
|
||||
<?php if (!$mmRows): ?>
|
||||
<div class="empty">
|
||||
<b>往年的这一天还没有记录</b>
|
||||
日子还长,等有了第一次,明年这时候就会自己冒出来。
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list">
|
||||
<?php foreach ($mmRows as $mm): ?>
|
||||
<li class="dash-row">
|
||||
<span class="dash-dot" style="background: <?= $mm['kind'] === 'trip' ? '#3F7D5A' : '#7A4E8C' ?>;"></span>
|
||||
<span class="dash-when"><?= (int)$mm['years_ago'] ?> 年前</span>
|
||||
<a class="dash-what" href="<?= e((string)$mm['href']) ?>">
|
||||
<?= $mm['kind'] === 'trip' ? '🧳 ' : '🎬 ' ?><?= e((string)$mm['title']) ?>
|
||||
</a>
|
||||
<span class="dash-tag"><?= e(substr((string)$mm['starts_at'], 0, 10)) ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* 首页卡片 · 观影(v62 新增)。
|
||||
*
|
||||
* 数据来自 `HomeCards::moviesData()`(最近 8 条,只查卡片要用的那几列 ——
|
||||
* 不用 Movie::all():那个会顺带查同行人与影评统计,卡片上都不显示)。
|
||||
*
|
||||
* @var array $cardData['movies'] 每项含 id/title/starts_at/poster_key/cinema_name
|
||||
*/
|
||||
$mvRows = (array)($cardData['movies'] ?? []);
|
||||
?>
|
||||
<div class="hc-body">
|
||||
<?php if (!$mvRows): ?>
|
||||
<p class="empty-hint">还没有观影记录。</p>
|
||||
<?php else: ?>
|
||||
<ul class="hc-mv-list">
|
||||
<?php foreach ($mvRows as $m): ?>
|
||||
<li class="hc-mv">
|
||||
<a class="hc-mv-poster" href="<?= e(url('movies/show', ['id' => (int)$m['id']])) ?>">
|
||||
<?php if (!empty($m['poster_key'])): ?>
|
||||
<img src="<?= e(media_url((string)$m['poster_key'])) ?>" alt="" loading="lazy">
|
||||
<?php else: ?>
|
||||
<span class="hc-mv-ph" aria-hidden="true">🎬</span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<div class="hc-mv-main">
|
||||
<a class="hc-mv-t" href="<?= e(url('movies/show', ['id' => (int)$m['id']])) ?>"
|
||||
title="<?= e((string)$m['title']) ?>"><?= e((string)$m['title']) ?></a>
|
||||
<span class="small muted">
|
||||
<?= e(substr((string)($m['starts_at'] ?? ''), 0, 10)) ?>
|
||||
<?php if (trim((string)($m['cinema_name'] ?? '')) !== ''): ?>
|
||||
· <?= e((string)$m['cinema_name']) ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* 首页卡片 · 近期任务(v62 从 templates/home.php 拆出来的)。
|
||||
*
|
||||
* 用户原话(v24 第 24 批 第 3 条):「右侧显示任务模块里面的数据,显示当前正在
|
||||
* 进行当中的所有的任务,除了显示任务的标题之外,还要显示他的状态。比如说正在
|
||||
* 进行中待确认等等。除了这之外,还要显示他所处的看板的名称。」
|
||||
*
|
||||
* 🔴 v53 第 2 条:待办也放进来,放在进行中的**下面**(不是混排)。
|
||||
* ⚠️ 每行前面有复选框,点了就地完成(AJAX,不跳页);表单与脚本在 home.php 末尾
|
||||
* 各一份 —— 那份**必须留着**,别跟着卡片一起删。
|
||||
* ⚠️ 标题的颜色跟着**看板色**(不是任务模块的主题色),见 v32 第 4 条那条注释。
|
||||
*/
|
||||
?>
|
||||
<div class="hc-body">
|
||||
<?php if (!$tasks): ?>
|
||||
<p class="empty-hint">没有进行中的任务,也没有待办。</p>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list tk-list">
|
||||
<?php foreach ($tasks as $t): ?>
|
||||
<?php $late = \App\Task::isOverdue($t['due_at'] ?? null); ?>
|
||||
<?php $tkColor = \App\Task::boardColorById((int)\App\Auth::familyId(),
|
||||
(int)($t['board_id'] ?? 0) ?: null); ?>
|
||||
<li class="dash-row tk-row" data-task-row="<?= (int)$t['id'] ?>"
|
||||
style="--tk-c: <?= e($tkColor) ?>;">
|
||||
<?php
|
||||
$tkId = (int)$t['id']; $tkStatus = (string)($t['status'] ?? 'todo');
|
||||
$tkBack = 'home'; $tkStatic = !\App\Auth::canWrite();
|
||||
require __DIR__ . '/../_task_check.php';
|
||||
?>
|
||||
<span class="dash-dot" style="background: <?= e($late ? 'var(--red)' : 'var(--tk-c)') ?>;"></span>
|
||||
<a class="dash-what" href="<?= e(url('tasks/show', ['id' => (int)$t['id']])) ?>"
|
||||
title="<?= e((string)$t['title']) ?>"
|
||||
data-fb-entry='<?= fb_entry_json([
|
||||
'title' => (string)$t['title'],
|
||||
'status_word' => \App\Task::statusWord((string)($t['status'] ?? 'todo')),
|
||||
'note' => (string)($t['note'] ?? ''),
|
||||
], url('tasks/show', ['id' => (int)$t['id']]),
|
||||
\App\Auth::canWrite() ? url('tasks/edit', ['id' => (int)$t['id']]) : '',
|
||||
[['截止', ($t['due_at'] ?? '') !== '' ? substr((string)$t['due_at'], 0, 16) : ''],
|
||||
['看板', trim((string)($t['board_name'] ?? ''))]]) ?>'
|
||||
style="color: <?= e($late ? 'var(--red)' : $tkColor) ?>;">
|
||||
<?= e((string)$t['title']) ?>
|
||||
</a>
|
||||
<span class="kb-st is-<?= e((string)($t['status'] ?? 'todo')) ?> is-static">
|
||||
<?= e(\App\Task::statusWord((string)($t['status'] ?? 'todo'))) ?>
|
||||
</span>
|
||||
<?php if (trim((string)($t['board_name'] ?? '')) !== ''): ?>
|
||||
<?php $bColor = \App\Task::boardColor($t['board_color'] ?? null, (string)$t['board_name']); ?>
|
||||
<span class="tk-board" style="--bc: <?= e($bColor) ?>;">
|
||||
<i class="tk-board-dot" aria-hidden="true"></i><?= e((string)$t['board_name']) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if ((int)($t['child_total'] ?? 0) > 0): ?>
|
||||
<span class="dash-tag"><?= (int)$t['child_done'] ?>/<?= (int)$t['child_total'] ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (($t['due_at'] ?? '') !== ''): ?>
|
||||
<span class="tk-due"><?= e(\App\Task::dueWord($t['due_at'] ?? null)) ?></span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
Reference in New Issue
Block a user