v71c:首页「通知消息」改成正规卡片,样式直接复用通知页
用户 2026-10-21:
「先把web端的首页修一下,通知消息这个,也是首页的卡片,可以调整顺序和大小。
另外,这个样式也不好看,我看个人头像点击后,通知消息那个页面的样式就不错,
直接复用就可以了。」
① 卡片化(HomeCards 注册表 + 默认布局 + 取数)
· TYPES 新增 'notif'(w4/h4,route=notifications,more=全部通知)
· DEFAULT_LAYOUT 钉在 brief 之后,与 days 并一行(宽 4)——
若插在 brief 与 agenda 之间,agenda/tasks 会被挤成一上一下(实测 C13 顶边 407 vs 751)
· data() 加 case 'notif' → 新增 notifData():最新 10 条 + 未读数,
与原来的取数口径一字不差;关掉卡片就不查这两条 SQL
· templates/home.php 删掉网格前那块写死区块(v30 第 5 条「必须在往年的今天前面」
这条约束作废 —— 那时还没有卡片体系);index.php 不再单独传 notifs/notifUnread
② 样式复用 templates/notifications/index.php
· 结构与通知页完全一致:头像在前 → 正文 → kind 图标在右(带 ntf-kind-{kind})
(旧首页那块是"图标在前、头像第二",正是用户说不好看的那一版)
· 新增 .hc-ntf 作用域只补"卡片比整页小"的三条差异:正文允许折两行(宽 2 时
单行省略会把正文全吃掉)、时间挪到正文下一行、未读数做小注脚
· 不带每条右侧「删除」按钮(4×4 的卡里塞两个按钮会把正文挤成两三个字),
删除在通知页做,卡片右上角有「全部通知」
③ 顺带修掉 v63 A1 抓住的一处:'notif' 的 mod 给 'home' 而不是空串
(通知不在 Module::LIST,但它是首页自己的卡片;空串会让「全部通知」成为
一排同色模块按钮里唯一白底 ghost 的那个)
⇒ TYPES docstring 立规矩:route 非空就必须有 mod,认不出归哪个模块就归 home
验证(familybook_push.sh 第 6 轮全绿,PUSH_RC=0,2h11m)
v62 首页卡片 56/0 · v62 反向验证 14/0(新增 M8–M12 五条注入)
b30 通知 16/0 · b30 反向验证 18/0
v63 40/0 · v63 反向验证 51/0 · e2e 1523/0 · 部署静态断言空跑 RC=0
同批带上上一轮 v70/v71 的日历修复(已单独验过,本轮流水线一并回归):
· v70 日历导入后条目不显示:+N 条从纯文本 span 改成真按钮,点开是当天全部
条目浮层;AJAX 新建后就地插入与服务端同形(回 entry,不让前端自己拼)
· v71 ICS 导入顺序:主事件 → 覆盖行 → EXDATE 由 Calendar::importIcs() 统一保证
This commit is contained in:
@@ -451,6 +451,45 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
/* 🔴 v70:把「每格全部条目」一次性送到前端(点 "+N 条" 时浮层直接用,
|
||||
不再发请求)。**只送当月格子里的日期**,量很小(一个月最多几百条)。
|
||||
⚠️ 用 `<script type="application/json">` 而不是把 JSON 塞进 data-*:
|
||||
data-* 属性里几千条会被 HTML 转义得又长又容易截断;
|
||||
JSON 里若出现 `</script>` 也会截断 —— 所以下面统一走 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;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<script type="application/json" id="calDayData"><?= json_encode(
|
||||
$dayData,
|
||||
JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP
|
||||
) ?></script>
|
||||
<div class="card cal-grid-card">
|
||||
<div class="cal-head">
|
||||
<?php /* ⚠️ 用户第 11 批 #6:「月视图,从周一开始。而不是周日。」
|
||||
@@ -469,8 +508,22 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
|
||||
data-new-at="<?= e((string)$cell['date']) ?>">
|
||||
<div class="cal-dnum"><?= (int)$cell['day'] ?></div>
|
||||
<div class="cal-evs">
|
||||
<?php $n = 0; foreach ($cell['events'] as $ev): $n++; ?>
|
||||
<?php if ($n <= 3): ?>
|
||||
<?php
|
||||
/* 🔴 v70(用户 2026-10-20 第 2 条):「日历导入进去了,但是不显示」。
|
||||
真根因不在这里的 3 条,而在**排序 + "+N 条"点不开**的组合:
|
||||
每格只画 3 条,而旧排序把"全天"(时间空串)排最前 ⇒ 农历/节气/
|
||||
节假日天天占满前三,用户自己导入的日程全被挤进 "+N 条",
|
||||
而那个 "+N 条" 只是个 <span>,点不开 = 完全看不到。
|
||||
两处都已修:① `Calendar::displayRank()` 让派生条目排最后;
|
||||
② 下面把 "+N 条" 换成**真按钮**,点开当天的全部条目浮层。
|
||||
⚠️ 保留 3 条不改成"全画":格子是固定高度(min-height:104px),
|
||||
15 条硬塞会把月网格撑得不像月历;超出部分交给浮层看。 */
|
||||
$cellEvents = $cell['events'];
|
||||
$shown = 3;
|
||||
$hiddenCnt = count($cellEvents) - $shown;
|
||||
?>
|
||||
<?php $n = 0; foreach ($cellEvents as $ev): $n++; ?>
|
||||
<?php if ($n <= $shown): ?>
|
||||
<?php
|
||||
$evKind = (string)($ev['kind'] ?? 'custom');
|
||||
$evRef = (int)($ev['ref_id'] ?? 0);
|
||||
@@ -491,8 +544,16 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php if (count($cell['events']) > 3): ?>
|
||||
<span class="cal-more">+<?= count($cell['events']) - 3 ?> 条</span>
|
||||
<?php if ($hiddenCnt > 0): ?>
|
||||
<?php /* 点开 = 当天全部条目(含被折叠的),一律走全站的条目详情浮层;
|
||||
每条都带 data-fb-entry,所以在浮层里还能继续点进详情/编辑/删除。
|
||||
⚠️ 用一个 `<a href>` 兜住无 JS 的情况:直接跳那天的日视图
|
||||
(日视图按时间网格铺开,不会再截断)。 */ ?>
|
||||
<button type="button" class="cal-more"
|
||||
data-cal-day="<?= e((string)$cell['date']) ?>"
|
||||
aria-label="看这一天全部 <?= count($cellEvents) ?> 条">+<?= $hiddenCnt ?> 条</button>
|
||||
<?php /* 浮层用的全部条目数据(服务端渲染好,点开时不需要再请求)。
|
||||
放在格子外面(display:none 的 script 里)—— 塞进格子会撑高月网格。 */ ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -611,12 +672,19 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
|
||||
var t = document.getElementById('ev-title');
|
||||
if (t) { t.focus(); }
|
||||
}
|
||||
/* v70:「当天全部条目」浮层里有「在这天加一条」,也要走同一个 openNew
|
||||
(它在该 IIFE 外面,看不到闭包里的 openNew)⇒ 暴露一个窗口入口。 */
|
||||
window.fbCalOpenNew = openNew;
|
||||
|
||||
/* 月视图格子 */
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.cal-cell[data-new-at]'), function (cell) {
|
||||
cell.addEventListener('click', function (ev) {
|
||||
// 点在小条上 = 想打开那条日程(<a> 的默认行为),不要抢
|
||||
if (ev.target.closest && ev.target.closest('.cal-ev')) { return; }
|
||||
/* 🔴 v70:点「+N 条」= 要看**当天的全部条目**,不是要新建日程。
|
||||
⚠️ 必须显式排除:它是个 <button>,不排除的话会**同时**触发
|
||||
"新建日程浮层" 与 "当天全部条目浮层" 两个浮层叠在一起。 */
|
||||
if (ev.target.closest && ev.target.closest('.cal-more')) { return; }
|
||||
if (!<?= $canWrite && $writable ? 'true' : 'false' ?>) { return; }
|
||||
openNew(cell.getAttribute('data-new-at'), '09', '00');
|
||||
});
|
||||
@@ -776,6 +844,51 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
|
||||
if (tip) { tip.textContent = msg; tip.className = 'cal-modal-tip err'; }
|
||||
}
|
||||
|
||||
/** 把一条 `fb_entry_json` 形态的条目补进 `#calDayData`(按天分组,幂等)。
|
||||
* ⚠️ 只补**还没在里面**的那条(同 ref + 同 occ 视为同一条)——
|
||||
* 否则连点两次「加上」或前端重放会把同一条塞两遍,清单里出现两条一样的。 */
|
||||
function pushDayData(day, entry) {
|
||||
var host = document.getElementById('calDayData');
|
||||
if (!host || !day || !entry) { return; }
|
||||
var all;
|
||||
try { all = JSON.parse(host.textContent || '{}') || {}; } catch (e) { return; }
|
||||
var list = all[day] || [];
|
||||
var same = false;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
if (String(list[i].ref || '') === String(entry.ref || '')
|
||||
&& String(list[i].occ || '') === String(entry.occ || '')
|
||||
&& String(list[i].title || '') === String(entry.title || '')) { same = true; break; }
|
||||
}
|
||||
if (!same) {
|
||||
/* 位置按服务端那套 `displayRank + 时间` 插(派生条目垫底),
|
||||
免得清单里的顺序与刷新后的顺序不一致。 */
|
||||
var rk = function (o) {
|
||||
var s = String(o.source || '');
|
||||
return s === 'event' ? 0 : 4; /* 派生条目一律 4,与服务端 displayRank 的兜底一致 */
|
||||
};
|
||||
var tm = function (o) {
|
||||
var rows = o.rows || [];
|
||||
for (var k = 0; k < rows.length; k++) {
|
||||
if (rows[k][0] === '时间') {
|
||||
var v = String(rows[k][1] || '');
|
||||
/* "YYYY-MM-DD HH:MM–HH:MM" / "YYYY-MM-DD 全天" → 取时刻部分比较 */
|
||||
var seg = v.slice(day.length).trim();
|
||||
return seg === '全天' ? '' : seg.slice(0, 5);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
var pos = list.length;
|
||||
for (var j = 0; j < list.length; j++) {
|
||||
if (rk(list[j]) > rk(entry)) { pos = j; break; }
|
||||
if (rk(list[j]) === rk(entry) && tm(list[j]) > tm(entry)) { pos = j; break; }
|
||||
}
|
||||
list.splice(pos, 0, entry);
|
||||
all[day] = list;
|
||||
try { host.textContent = JSON.stringify(all); } catch (e2) { /* 序列化失败就别动 */ }
|
||||
}
|
||||
}
|
||||
|
||||
/** 月视图:往 data-new-at="YYYY-MM-DD" 那个格子里塞一条小条 */
|
||||
function putMonth(ev) {
|
||||
var day = String(ev.starts_at || '').slice(0, 10);
|
||||
@@ -820,16 +933,27 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
|
||||
/* 再按"只显示前 3 条"收口,与服务端渲染对齐(否则会多出一条)。 */
|
||||
var links = box.querySelectorAll('.cal-ev');
|
||||
var more = box.querySelector('.cal-more');
|
||||
/* 🔴 v70:新条目同步进 `#calDayData` —— 否则「+N 条」点开是**旧**清单,
|
||||
刚建的这条不在里面(刷新一次又有了)。放在收口之前做,跟服务端渲染时
|
||||
"先把当天全部条目收进数据岛、再只画前 3 条"的顺序一致。 */
|
||||
if (ev.entry) { pushDayData(day, ev.entry); }
|
||||
for (var j = 0; j < links.length; j++) {
|
||||
links[j].style.display = (j < 3) ? '' : 'none';
|
||||
}
|
||||
if (links.length > 3) {
|
||||
var extra = links.length - 3;
|
||||
if (!more) {
|
||||
more = document.createElement('span');
|
||||
/* 🔴 v70(用户 2026-10-20 第 2 条):「+N 条」原来是颗 `<span>`,
|
||||
**点不动** ⇒ 被它折叠掉的条目永远看不到("导进去了但不显示")。
|
||||
这里必须造**按钮**并带 `data-cal-day`(与模板渲染同形),
|
||||
否则新建一条之后,这个格子就退回"点不动"。 */
|
||||
more = document.createElement('button');
|
||||
more.type = 'button';
|
||||
more.className = 'cal-more';
|
||||
box.appendChild(more);
|
||||
}
|
||||
more.setAttribute('data-cal-day', day);
|
||||
more.setAttribute('aria-label', '看这一天全部 ' + links.length + ' 条');
|
||||
more.textContent = '+' + extra + ' 条';
|
||||
} else if (more) {
|
||||
more.parentNode.removeChild(more);
|
||||
@@ -1208,3 +1332,136 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<?php /* ==================== 当天全部条目浮层(v70) ====================
|
||||
用户 2026-10-20 第 2 条:「日历导入进去了,但是不显示」。
|
||||
月视图每格只画 3 条(格子是固定高度,全画会把月历撑变形),
|
||||
但原来的 "+N 条" 是**一颗点不动的 <span>** ⇒ 被折叠的条目**永远看不到**。
|
||||
这里给那颗按钮一个真正的去处。
|
||||
|
||||
⚠️ 为什么不直接复用条目详情浮层(`fbEntryModal`):
|
||||
那是"**一条**日程的详情/编辑/删除",一次只画一条;
|
||||
这里要的是"**当天的清单**",点其中任意一条才进那条的详情。
|
||||
两者叠用是对的:先出清单,点清单里某条 → 调 `window.fbEntryOpen()` 进详情层。
|
||||
⚠️ 详情层是 `position:fixed` 的高层,清单层在它下面 —— 关掉详情层会**回到清单**,
|
||||
符合"我从当天的清单里点进来的"这个来路(不用再点一次 +N 条)。
|
||||
⚠️ 数据直接读上面那个 `<script id="calDayData">`:不发请求、点开即时。
|
||||
⚠️ 无 JS 兜底:那颗按钮外面本来是 <button>,禁用 JS 时它不会有反应 —
|
||||
这是可接受的降级(此时"+N 条"本来也没有别的语义),
|
||||
因为**没有 JS 的浏览器根本也不会有"点格子新建日程"那一套**,
|
||||
用户会直接用「周 / 天」视图看全部条目(那两个视图不截断)。 */ ?>
|
||||
<div class="cal-modal cal-day-modal" id="calDayModal" role="dialog" aria-modal="true"
|
||||
aria-label="当天全部日程" hidden>
|
||||
<div class="cal-modal-panel">
|
||||
<div class="cal-modal-head">
|
||||
<b id="calDayTitle">当天的日程</b>
|
||||
<button type="button" class="cal-modal-x" id="calDayClose" aria-label="关闭">✕</button>
|
||||
</div>
|
||||
<div class="cal-modal-body cal-day-body" id="calDayBody"></div>
|
||||
<div class="cal-modal-foot cal-day-foot">
|
||||
<?php /* 浮层里就地再建一条 —— 清单本来就在"这天",比退出去点格子顺手。
|
||||
⚠️ 没写权限时**不渲染**(不是 hidden):只读家庭里这颗按钮点了也没用,
|
||||
留着反而像坏了。 */ ?>
|
||||
<?php if ($canNew): ?>
|
||||
<button type="button" class="btn btn-sm" id="calDayAdd">+ 在这天加一条</button>
|
||||
<?php endif; ?>
|
||||
<span class="cal-modal-tip" id="calDayTip"></span>
|
||||
<a class="btn btn-ghost btn-sm" id="calDayMore" href="#">在日视图里看</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
var host = document.getElementById('calDayModal');
|
||||
if (!host) { return; }
|
||||
var body = document.getElementById('calDayBody');
|
||||
var title = document.getElementById('calDayTitle');
|
||||
var link = document.getElementById('calDayMore');
|
||||
|
||||
function esc(s) {
|
||||
return String(s == null ? '' : s)
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function dayData() {
|
||||
var el = document.getElementById('calDayData');
|
||||
if (!el) { return {}; }
|
||||
try { return JSON.parse(el.textContent || '{}') || {}; } catch (e) { return {}; }
|
||||
}
|
||||
|
||||
function openDay(day) {
|
||||
var all = dayData()[day] || [];
|
||||
if (!all.length) { return; }
|
||||
title.textContent = day + ' · 共 ' + all.length + ' 条';
|
||||
var html = '';
|
||||
all.forEach(function (it, i) {
|
||||
/* 时间行:全天显示"全天",有时刻显示 HH:MM–HH:MM(与详情浮层同一口径) */
|
||||
var when = '';
|
||||
var rows = it.rows || [];
|
||||
for (var k = 0; k < rows.length; k++) {
|
||||
if (rows[k][0] === '时间') { when = rows[k][1]; break; }
|
||||
}
|
||||
if (when.indexOf(day) === 0) { when = when.slice(day.length).trim(); }
|
||||
var src = (it.source === 'event') ? '' : ' is-derived';
|
||||
html += '<button type="button" class="cal-day-item' + src + '" data-day-idx="' + i + '">'
|
||||
+ '<span class="cal-day-when">' + esc(when || '全天') + '</span>'
|
||||
+ '<span class="cal-day-name">' + esc(it.title || '(无标题)') + '</span>'
|
||||
+ '</button>';
|
||||
});
|
||||
body.innerHTML = html;
|
||||
document.getElementById('calDayTip').textContent = '';
|
||||
link.setAttribute('href', <?= json_encode(url('calendar', ['view' => 'day'])) ?> + '&d=' + encodeURIComponent(day));
|
||||
host.hidden = false;
|
||||
/* 记录当前是哪一天,点清单项时要按它取数据 */
|
||||
host.setAttribute('data-day', day);
|
||||
}
|
||||
|
||||
function closeDay() { host.hidden = true; }
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest && e.target.closest('.cal-more');
|
||||
if (btn) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); /* 别让"点格子新建日程"也跟着触发 */
|
||||
openDay(btn.getAttribute('data-cal-day') || '');
|
||||
return;
|
||||
}
|
||||
if (e.target.closest && e.target.closest('#calDayClose')) {
|
||||
e.preventDefault(); closeDay(); return;
|
||||
}
|
||||
/* 清单里"在这天加一条":把当前这天交给新建浮层(09:00 起,与服务端
|
||||
"点月格"同口径),并先收起清单 —— 两层浮层不能叠着。 */
|
||||
if (e.target.closest && e.target.closest('#calDayAdd')) {
|
||||
e.preventDefault();
|
||||
var d0 = host.getAttribute('data-day') || '';
|
||||
closeDay();
|
||||
if (d0 && typeof window.fbCalOpenNew === 'function') { window.fbCalOpenNew(d0, '09', '00'); }
|
||||
return;
|
||||
}
|
||||
/* 点清单里某一条 → 交给条目详情浮层。
|
||||
走 `fbEntryOpenData`(v70 新增的"按数据对象开"入口)——
|
||||
清单项只是文字行,页面上没有对应的 `[data-fb-entry]` 元素可点。 */
|
||||
var item = e.target.closest && e.target.closest('.cal-day-item');
|
||||
if (item) {
|
||||
e.preventDefault();
|
||||
var day = host.getAttribute('data-day') || '';
|
||||
var list = dayData()[day] || [];
|
||||
var one = list[parseInt(item.getAttribute('data-day-idx'), 10)];
|
||||
if (one && typeof window.fbEntryOpenData === 'function') {
|
||||
closeDay(); /* 先收清单,再开详情 —— 否则两层叠在一起 */
|
||||
window.fbEntryOpenData(one);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.target === host) { closeDay(); }
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Escape') { return; }
|
||||
if (host.hidden) { return; }
|
||||
e.preventDefault(); closeDay();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
+6
-45
@@ -19,9 +19,12 @@
|
||||
*
|
||||
* ⚠️ 别在模板里自己算"该多宽":宽高由 `$homeCards`(用户设置)给,
|
||||
* 模板只负责把类名拼对(`hc-w4 hc-h8`)。
|
||||
* ⚠️ 通知块仍在**卡片网格之前**:v30 第 5 条要求它出现在「往年的今天」前面,
|
||||
* 而"往年的今天"现在是网格里的一张卡片,位置由用户定 —— 放在网格前是唯一
|
||||
* 无论用户怎么摆都成立的方案。
|
||||
*
|
||||
* 🔴 v71c(用户 2026-10-21)推翻了一条旧约束:「通知消息这个,也是首页的卡片,
|
||||
* 可以调整顺序和大小。」—— 它原来在本文件里是一块**网格之前**的写死区块
|
||||
* (v30 第 5 条当时还没有卡片体系,只能用"排在网格前"保证它在「往年的今天」上面)。
|
||||
* 现在它是一张**正规卡片**(`_card_notif.php`):位置/宽高/开不开都由用户定。
|
||||
* ⇒ 这条"必须在网格前"的约束**到此为止**,别再按它往网格上加写死区块。
|
||||
*
|
||||
* @var array $homeCards HomeCards::layout() 的结果(每项 k/w/h/n)
|
||||
* @var array $cardData HomeCards::data() 取的那几张卡片的额外数据
|
||||
@@ -78,48 +81,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php /* ==================== 通知消息(v30 第 5 条)====================
|
||||
用户原话:「…发一个通知,通知出现在首页中,**往年今天的前面**。」
|
||||
⇒ 放在卡片网格**之前**:往年的今天是网格里的一张卡片,位置由用户摆,
|
||||
只有"在网格前面"才能保证永远在它上面。
|
||||
⚠️ 没有通知时整块不渲染 —— 首页是"每天第一眼看的地方",不要用一个空盒子占一屏。 */ ?>
|
||||
<?php if (!empty($notifs)): ?>
|
||||
<section class="section">
|
||||
<div class="page-head" style="margin-bottom:12px">
|
||||
<h1 style="font-size:15px">
|
||||
通知消息
|
||||
<?php if ($notifUnread > 0): ?><span class="sub"><?= (int)$notifUnread ?> 条未读</span><?php endif; ?>
|
||||
</h1>
|
||||
<div class="actions">
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('notifications')) ?>">全部通知</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-pad">
|
||||
<ul class="ntf-list ntf-list-home">
|
||||
<?php foreach ($notifs as $n): ?>
|
||||
<?php
|
||||
$actorName = \App\Nickname::display(
|
||||
(int)$meId,
|
||||
['name' => $n['actor_name'] ?? '', 'my_nickname' => $n['actor_nick'] ?? '']
|
||||
);
|
||||
if (trim((string)$actorName) === '') { $actorName = '有人'; }
|
||||
?>
|
||||
<li class="ntf-row ntf-row-<?= e((string)($n['kind'] ?? '')) ?><?= empty($n['is_read']) ? ' is-unread' : '' ?>">
|
||||
<span class="ntf-kind" aria-hidden="true"><?= \App\Notification::icon((string)($n['kind'] ?? '')) ?></span>
|
||||
<img class="ntf-av" src="<?= e(avatar_url($n['actor_avatar'] ?? null)) ?>" alt="">
|
||||
<a class="ntf-main" href="<?= e(\App\Notification::href($n)) ?>">
|
||||
<span class="ntf-title">
|
||||
<b><?= e((string)$actorName) ?></b><?= e(\App\Notification::bodyWithoutActor((string)$n['title'])) ?>
|
||||
</span>
|
||||
<span class="ntf-time"><?= e(fb_time_ago((string)$n['created_at'])) ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php /* ==================== 卡片网格(v62)====================
|
||||
⚠️ `require` 进来的每个 `_card_*.php` 与这里**同一作用域**(共享 $tasks / $todayEvents
|
||||
/ $cardData …):这是刻意的 —— 卡片的取数与首页的取数必须同源,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* 首页卡片 · 通知消息(v71c 用户 2026-10-21 从 templates/home.php 搬进来)。
|
||||
*
|
||||
* 用户原话:「先把 web 端的首页修一下,通知消息这个,也是首页的卡片,可以调整顺序
|
||||
* 和大小。另外,这个样式也不好看,我看个人头像点击后,通知消息那个页面的
|
||||
* 样式就不错,直接复用就可以了。」
|
||||
*
|
||||
* ⇒ 两件事一起做了:
|
||||
* ① **卡片化**:不再是网格外那块写死区块,位置/宽高都由 `home_cards` 决定;
|
||||
* ② **样式复用**:结构与 `templates/notifications/index.php` **一模一样** ——
|
||||
* 头像在最前、正文在中间、kind 图标在右(带 `ntf-kind-{kind}` 修饰)。
|
||||
* 旧版首页那块是"图标在最前、头像在第二位",正是用户说"不好看"的那一版。
|
||||
*
|
||||
* ⚠️ 三处与通知页**故意不同**(都是"卡片比整页小"带来的):
|
||||
* · 不带每条右侧的「删除」按钮 —— 4 宽 × 4 高的卡片里塞两个按钮,正文会被挤成
|
||||
* 两三个字(用户抱怨的正是"不好看")。删除在通知页做,卡片右上角有「全部通知」。
|
||||
* · 标题不加旧首页那块用的 `ntf-list` home 修饰类 —— 那一类会把行距压到 6px,
|
||||
* 是给"网格外那条细长清单"用的;卡片里沿用通知页的行距,才叫"样式直接复用"。
|
||||
* · 正文允许折两行(`.hc-ntf` 作用域,见 app.css):卡片宽度可能只有 2 格,
|
||||
* 单行省略会把「有人把你加进了观影「…」」全吃掉,只剩一个名字。
|
||||
*
|
||||
* ⚠️ 没有通知时**整张卡片都不渲染**(返回空串是做不到的 —— 卡片外壳由 home.php 拼)。
|
||||
* 这里给一个空提示,与其它卡片(观影/读书/纪念日)的空态同一套写法。
|
||||
* 真正"不想要它"的人,去「首页卡片」里把它关掉(那张卡片会从网格里消失)。
|
||||
*
|
||||
* @var array $cardData['notif'] ['rows' => 通知行(最新在前), 'unread' => 未读数]
|
||||
*/
|
||||
$ntfRows = (array)($cardData['notif']['rows'] ?? []);
|
||||
$ntfUnread = (int)($cardData['notif']['unread'] ?? 0);
|
||||
?>
|
||||
<div class="hc-body">
|
||||
<?php if (!$ntfRows): ?>
|
||||
<p class="empty-hint">还没有通知。别人在观影、旅程里把你加进去时,这里就会有消息。</p>
|
||||
<?php else: ?>
|
||||
<?php if ($ntfUnread > 0): ?>
|
||||
<p class="hc-ntf-note"><?= (int)$ntfUnread ?> 条未读</p>
|
||||
<?php endif; ?>
|
||||
<ul class="ntf-list hc-ntf">
|
||||
<?php foreach ($ntfRows as $n): ?>
|
||||
<?php
|
||||
// 谁发起的:优先用"我叫他什么"(与全站称呼口径一致)
|
||||
$ntfActor = \App\Nickname::display(
|
||||
(int)$meId,
|
||||
['name' => $n['actor_name'] ?? '', 'my_nickname' => $n['actor_nick'] ?? '']
|
||||
);
|
||||
if (trim((string)$ntfActor) === '') { $ntfActor = '有人'; }
|
||||
$ntfKind = (string)($n['kind'] ?? '');
|
||||
?>
|
||||
<li class="ntf-row ntf-row-<?= e($ntfKind) ?><?= empty($n['is_read']) ? ' is-unread' : '' ?>">
|
||||
<img class="ntf-av" src="<?= e(avatar_url($n['actor_avatar'] ?? null)) ?>" alt="">
|
||||
<a class="ntf-main" href="<?= e(\App\Notification::href($n)) ?>">
|
||||
<span class="ntf-title">
|
||||
<b><?= e((string)$ntfActor) ?></b>
|
||||
<?= e(\App\Notification::bodyWithoutActor((string)$n['title'])) ?>
|
||||
</span>
|
||||
<span class="ntf-time"><?= e(fb_time_ago((string)$n['created_at'])) ?></span>
|
||||
</a>
|
||||
<span class="ntf-kind ntf-kind-<?= e($ntfKind) ?>" aria-hidden="true">
|
||||
<?= \App\Notification::icon($ntfKind) ?>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -41,6 +41,8 @@ $hcHs = \App\HomeCards::HEIGHTS;
|
||||
<b>宽</b>:2 = 一排 4 张,4 = 一排 2 张,6 = 一大一小,8 = 独占一排。
|
||||
<b>高</b>:2 / 4 / 6 / 8 行(一行 76px),卡片内容多的时候内部会自己滚。
|
||||
<br>关掉的卡片<b>不会丢数据</b>,只是不摆在首页上。
|
||||
<br>「通知消息」与其它卡片一样可以调顺序和大小 —— 宽 2 时正文会折两行,
|
||||
想看完整的一句话就给它 4 格或更宽。
|
||||
</p>
|
||||
|
||||
<ol class="hc-set-list" id="hcSetList">
|
||||
|
||||
Reference in New Issue
Block a user