v43 第 43 批:六条反馈(界面 5 + 条目详情浮层)

用户 2026-10-11 反馈六条,逐条定位真根因后修复:

① 纪念日「日期」多出一个控件 —— 只保留最新的那个
   两层根因:(a)原生 `input[type=date]` 当初没包在 `.dt-native` 里,
   v42 那条"AntD 挂上后收起原生框"的选择器根本不命中;(b)更要命的是
   `.dt-native` 基类写着 `display: contents` —— 该元素**没有盒子**,
   给它 position/width 完全无效 ⇒ 必须把 display 显式改回 block 才收得起来。

② 编辑纪念日把底层列表页弄没 —— 「修改」原来是整页跳 `days/edit`
   (套 layout = 一张只有弹框的空白页)。改为 `?embed=1` 只吐弹框那一块,
   列表页 JS 取回来替换 `#anNewCard` 并打开 —— 与「新建」同一观感。
   内嵌形态标 `$anModalOnly` 以免打 standalone 标记(关闭时不再跳页)。

③ 拖图裁剪的层被弹框盖住 —— `.crop-overlay` 是 z-index:100 而 `.cal-modal`
   是 200 ⇒ 裁剪层被整块盖住。提到 260(与 v41 的 .cn-modal 同一处理)。

④ 行程弹框里"参与的人"弹不出选人框 —— person_picker.js 只绑定一次,
   而弹框表单是 AJAX 片段 ⇒ 里面的 `.pp` 从未被 init。三件事一起:
   暴露 `window.fbInitPickers()`(幂等)+ layout 暴露 `window.fbLoadPicker()`
   (脚本是按需加载的)+ 塞完片段后补调初始化。

⑤ 日程新建排版/控件/文案 —— 改走观影式 `.mv-grid`(标签对齐 + 冒号);
   起止补上 `$dtPlugin='antd'`(试点正式推广到日程);标签「时间范围」→「开始 / 结束」;
   删三句提示(结束留空 / 多选提醒 / 全天)。

⑥ 条目详情浮层(首页日程 / 首页近期任务 / 日程页条目)——
   点条目不再直接跳页,弹浮层显示详情;「详细信息」跳该来源详情页;
   「编辑」就地换成该来源的编辑表单(四处 `?embed=1` 片段)。
   新增 `templates/_entry_popup.php` + `assets/js/entry_popup.js` +
   `fb_entry_json()`(三个入口共用一个编码函数)。

验证:php -l 157 文件全过;div 深度 33 组合全配平;片段体检 20 片段无越界;
本地 e2e 1510/0;v43 真机 33/0;v43 反向验证 8/0;v38 36/0;v42 33/0;
b31/b32 反向验证 8/0 与 5/0;部署断言本地空跑 RC=0;完整流水线 PUSH_RC=0。
This commit is contained in:
2026-10-12 00:24:20 +08:00
parent 81dfacaf1b
commit 8c651b43eb
17 changed files with 1036 additions and 161 deletions
+48 -2
View File
@@ -216,7 +216,13 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
</div>
<?php /* v24 第 6 条:字段按 iCalendar / CalDAV 的 VEVENT 定(见 _event_fields.php)。
新建时「全天」默认**不勾**(用户原话)。开始时间默认:
带了 new_at 就用它(点日历的那一格),否则今天 09:00。 */ ?>
带了 new_at 就用它(点日历的那一格),否则今天 09:00。
🔴 v43(用户 2026-10-11 第 5 条):「新建日程页面的排版还是不对,
也是一样的:把标题放在前面,后面加冒号,再跟上输入框。」
⇒ 用观影/行程那套 `.mv-grid`(标签 62px 右对齐 + CSS ::after 冒号 +
控件占满剩余宽度)包住字段块。
`$evMvStyle = true` 让字段块输出 `.mv-row` / `.mv-lb`
而不是 `.field` / `<label>`;编辑页不传,保持原两列。 */ ?>
<?php
// 点的位置优先;没点就"月视图当月 → 该月 1 号 / 本月 → 今天"的 09:00
$defDay = $newAt !== '' ? substr($newAt, 0, 10)
@@ -227,7 +233,11 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
$evFields = ['starts_at' => $defDay . ' ' . $defHM . ':00',
'ends_at' => $defDay . ' ' . $defEndH . ':' . substr($defHM, 3, 2) . ':00'];
$evAllDay = false;
$evMvStyle = true; // 观影式排版(用户第 5 条)
echo '<div class="mv-grid">';
require __DIR__ . '/_event_fields.php';
echo '</div>';
unset($evMvStyle);
?>
</div><?php /* /cal-modal-body */ ?>
<div class="cal-modal-foot">
@@ -273,7 +283,17 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
<?php foreach (($cols ?? []) as $i => $c): ?>
<div class="tg-adcell">
<?php foreach (($tgAll[$i] ?? []) 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="cal-ev is-allday" data-cal="<?= (int)$ev['cal'] ?>"
data-fb-entry='<?= fb_entry_json($ev, (string)($ev['href'] ?? ''), $evEdit) ?>'
style="background: <?= e((string)$ev['color']) ?>;"
href="<?= e((string)$ev['href']) ?>"
title="<?= e((string)$ev['title']) ?>"><?= e((string)$ev['title']) ?></a>
@@ -322,7 +342,23 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
列数 1/2/3/4 是运行时才知道的,类名会爆炸;
而且模板已经在内联 top/height 了,同一套坐标系最不容易错。 */ ?>
<?php foreach (($tgrid['timed'][$i] ?? []) as $ev): ?>
<?php /* 🔴 v43(用户第 6 条):点条目弹详情浮层(不再直接跳页)。
数据由 fb_entry_json() 统一编(三个入口共用一个函数)。
「详细信息」跳该来源的详情页;「编辑」取该来源的编辑表单片段。
⚠️ 编辑地址一律带可写判断:只读的(农历/节假日)不给「编辑」。 */ ?>
<?php
$evKind = (string)($ev['kind'] ?? 'custom');
$evRef = (int)($ev['ref_id'] ?? 0);
$evDet = (string)($ev['href'] ?? '');
$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]); }
// trip 的条目指向"某个旅程里的某段行程",编辑器在旅程详情页里 —— 这里不给「编辑」
elseif ($evKind === 'anniv' && $evRef > 0) { $evEdit = url('days/edit', ['id' => $evRef, 'embed' => 1]); }
?>
<a class="tg-block" data-cal="<?= (int)$ev['cal'] ?>"
data-fb-entry='<?= fb_entry_json($ev, $evDet, $evEdit) ?>'
style="background: <?= e((string)$ev['color']) ?>;
top: <?= (float)$ev['top'] ?>%; height: <?= (float)$ev['height'] ?>%;
left: calc(2px + <?= (float)($ev['left'] ?? 0) ?>%);
@@ -357,8 +393,18 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
<div class="cal-evs">
<?php $n = 0; foreach ($cell['events'] as $ev): $n++; ?>
<?php if ($n <= 3): ?>
<?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="cal-ev<?= empty($ev['all_day']) ? '' : ' is-allday' ?>"
data-cal="<?= (int)$ev['cal'] ?>"
data-fb-entry='<?= fb_entry_json($ev, (string)($ev['href'] ?? ''), $evEdit) ?>'
style="background: <?= e((string)$ev['color']) ?>;"
href="<?= e((string)$ev['href']) ?>"
title="<?= e((string)$ev['title']) ?>">
@@ -409,7 +455,7 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
月视图:一格 = 一天 → 开始时间用那天 09:00("当日默认"比 00:00 更像个日程)。
周/日视图:按鼠标在**那一列里的纵向位置**反算时刻,最接近的半小时 ——
点在"下午三点那一段"就该得到 15:00,这是"点哪儿建哪儿"的全部意义。 */
var startField = document.querySelector('.field.ev-dt[data-dt]');
var startField = document.querySelector('.ev-dt[data-dt]');
var startDate = startField ? startField.querySelector('input.dt-date') : null;
var startHour = startField ? startField.querySelector('select.dt-hour') : null;
var startMin = startField ? startField.querySelector('select.dt-minute') : null;