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
+91 -47
View File
@@ -17,31 +17,54 @@
* 用法(include 前设好):
* $evFields 正在编辑的日程(新建时传空数组)
* $evAllDay 全天的默认值(新建默认**不勾**,用户 2026-10-02 第 6 条)
* $evMvStyle 🔴 v43:「标签 62px 右对齐 + 冒号 + 控件」的**观影式**排版开关。
* 新建日程那个弹框要它(用户:日程新建页排版和观影/行程一样);
* 编辑页(`calendar/event.php`)继续用原来的 `.form-grid` 两列。
*/
$evF = is_array($evFields ?? null) ? $evFields : [];
$evAllDayDefault = (bool)($evAllDay ?? false);
$evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evAllDayDefault;
/* 🔴 v43(用户 2026-10-11 第 5 条):观影式排版开关。 */
$evMv = !empty($evMvStyle);
/* 标签包装:观影式用 `.mv-row` + `.mv-lb`(冒号由 CSS ::after 给);
否则用原来的 `.field` + `<label>`。
⚠️ 抽成闭包是为了**不在下面每处 if/else 手抄两遍结构** ——
抄两遍必然有一天两边不同步(v37 那种"改了 A 忘了 B"的坑)。
⚠️ 「必填星号」作为**参数**传(不要用 `use` 捕获 —— 闭包定义时那个变量还没赋值,
捕获到的是 null,星号永远不出现)。 */
$evRowOpen = static function (string $full = '') use ($evMv): string {
return '<div class="' . ($evMv ? 'mv-row' : 'field') . ($full !== '' ? ' ' . $full : '') . '">';
};
$evLab = static function (string $text, string $for = '', bool $star = false) use ($evMv): string {
if ($evMv) {
// 观影式:标签是 <span class="mv-lb">,冒号由 CSS ::after 给
return '<span class="mv-lb">' . $text . ($star ? '<i>*</i>' : '') . '</span>';
}
return '<label' . ($for !== '' ? ' for="' . e($for) . '"' : '') . '>' . $text . ($star ? ' *' : '') . '</label>';
};
?>
<div class="field full">
<label for="ev-title">标题 *</label>
<?php echo $evRowOpen('full'); ?>
<?= $evLab('标题', 'ev-title', true) ?>
<input class="input" id="ev-title" name="title" required
value="<?= e((string)($evF['title'] ?? '')) ?>" placeholder="如:家长会">
</div>
<div class="field">
<label for="ev-location">地点</label>
<?php echo $evRowOpen(); ?>
<?= $evLab('地点', 'ev-location') ?>
<input class="input" id="ev-location" name="location"
value="<?= e((string)($evF['location'] ?? '')) ?>" placeholder="如:学校三楼会议室">
</div>
<div class="field">
<label>提醒</label>
<?php echo $evRowOpen(); ?>
<?= $evLab('提醒') ?>
<?php /* v24 第 20 批 第 8 条:**多选**(用户原话:「一个日程,我可以选择让它
5分钟提醒,10分钟提醒等等。」)。
⚠️ 用 checkbox 而不是 `<select multiple>`:后者在手机上要"按住再点",
几乎没法用(任务共享那个 multiple 下拉就是因此被换掉的)。
⚠️ 字段名必须是 `reminder_minutes[]` —— Calendar::reminderMinutes() 认得数组。
写成 `reminder_minutes`(不带 [])会让 PHP 只留最后一个勾选。 */
写成 `reminder_minutes`(不带 [])会让 PHP 只留最后一个勾选。
🔴 v43(用户第 5 条):他要求把这一栏底下那句「多选与不提醒」的说明去掉
⇒ 这里删掉那个 `.tip`(多选语义由复选框本身表达)。 */
$curReminders = \App\Calendar::reminderList($evF['reminder_minutes'] ?? null); ?>
<div class="rem-picks">
<?php foreach (\App\Calendar::REMINDERS as $mins => $word): ?>
@@ -52,7 +75,6 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
</label>
<?php endforeach; ?>
</div>
<span class="tip">可以选多个(到点各响一次);一个都不选就是不提醒。</span>
</div>
<?php /* v24 第 5、6 条:起止各一个「日期 + 时分秒」合在一起的控件(全局统一),
@@ -74,43 +96,58 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
修法:**结束那一端不再默认藏起来**,范围控件一次性就挂上。
起止成对本来就该一起填。想表达"只有一个时刻"的话,范围控件 clear 一下即可。
⚠️ 顺带删掉「+ 添加结束时间 / 去掉结束时间」那套开关:
它切换显隐会让范围控件反复重挂;范围控件自己就能表达"没有结束"。 */ ?>
<div class="dt-range">
它切换显隐会让范围控件反复重挂;范围控件自己就能表达"没有结束"。
🔴🔴 v43(用户 2026-10-11 第 5 条):「开始和结束时间也没有用我们最新的控件,
直接就是四个字"时间范围"。」
—— 两处真缺陷一起修:
① 这个字段块**没传 `$dtPlugin='antd'`** ⇒ 用户看到的还是年/月/日 + 时/分
五个原生下拉,而不是他点名要的 Ant Design 控件;
② 标签写的是「时间范围」四个字,没告诉人"这是开始 / 结束"。
⇒ `$dtPlugin='antd'`(与观影/行程同一套:面板有可点的时/分列、中文「确定」),
标签改「开始 / 结束」(并去掉那颗必填星号的位置歧义 —— 只标在开始上)。
⚠️ 契约一字未改:隐藏字段仍是 `*_date/_hour/_minute`;antd 拉不动时
自动退回自建下拉(渐进增强)。 */ ?>
<div class="dt-range<?= $evMv ? ' mv-row' : '' ?>">
<?php /* ⚠️ 标签只有**一个**,挂在范围容器上(不是各挂各的):
升级成一个范围控件之后,"开始"与"结束"就是同一个控件的两个端点,
两个标签会变成"看着像两个字段、其实是一个"的错觉。 */ ?>
<span class="dt-range-label">开始 / 结束 *</span>
<?php if ($evMv): ?>
<span class="mv-lb dt-range-label">开始 / 结束<i>*</i></span>
<?php else: ?>
<span class="dt-range-label">开始 / 结束 *</span>
<?php endif; ?>
<div class="field ev-dt" data-dt>
<?php $dtName = 'starts_at'; $dtId = 'ev-start';
$dtValue = $evF['starts_at'] ?? null; $dtRequired = true;
$dtRange = 'start';
$dtRange = 'start'; $dtPlugin = 'antd';
require __DIR__ . '/../_datetime.php'; ?>
</div>
<div class="field ev-dt" data-dt id="ev-end-field">
<?php $dtName = 'ends_at'; $dtId = 'ev-end';
$dtValue = $evF['ends_at'] ?? null; $dtRequired = false;
$dtRange = 'end';
$dtRange = 'end'; $dtPlugin = 'antd';
require __DIR__ . '/../_datetime.php'; ?>
</div>
</div><?php /* /dt-range */ ?>
<span class="field-hint" style="display:block;margin:-4px 0 10px">
结束留空就表示没有明确的结束(只占开始那一刻/那一天)。
</span>
<?php /* 🔴 v43(用户第 5 条):他要求把「结束留空即无明确结束」的那条提示去掉
⇒ 那句 `.field-hint` 整块删除(不再输出)。 */ ?>
<div class="field">
<label>
<?php echo $evRowOpen(); ?>
<label class="chk-inline">
<input type="checkbox" id="ev-allday" name="all_day" value="1"
<?= $evIsAllDay ? 'checked' : '' ?>>
全天
<span>全天</span>
</label>
<span class="field-hint">勾上就不管具体几点,只占这一天</span>
<?php /* 🔴 v43(用户第 5 条):他要求把「全天只管日期、不管几点」的说明也去掉
⇒ 那句 `.field-hint` 整块删除。 */ ?>
</div>
<?php /* 用户 2026-10-03 第 5、6 条:重复日程。
放在「全天」之后的自然位置:先定"哪一天/几点",再定"要不要重复"。
周期用 FREQ 记号,结束条件三档(一直 / N 次 / 到某天),另加"工作日"。 */ ?>
<div class="field">
<label for="ev-freq">重复</label>
<?php echo $evRowOpen(); ?>
<?= $evLab('重复', 'ev-freq') ?>
<select class="select" id="ev-freq" name="freq">
<?php $rp = \App\Repeat::parse((string)($evF['rrule'] ?? '')); ?>
<?php foreach (\App\Repeat::FREQS as $k => $w): ?>
@@ -119,29 +156,35 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
</select>
</div>
<div class="field" id="ev-rep-end-box" hidden>
<label for="ev-rep-end">结束条件</label>
<select class="select" id="ev-rep-end" name="rep_end">
<?php foreach (\App\Repeat::ENDS as $k => $w): ?>
<option value="<?= e($k) ?>"<?= $rp['count'] > 0 ? '' : '' ?>><?= e($w) ?></option>
<?php endforeach; ?>
</select>
<?php echo $evRowOpen(); ?>
<?= $evLab('结束条件', 'ev-rep-end') ?>
<div id="ev-rep-end-box" hidden>
<select class="select" id="ev-rep-end" name="rep_end">
<?php foreach (\App\Repeat::ENDS as $k => $w): ?>
<option value="<?= e($k) ?>"><?= e($w) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="field" id="ev-rep-count-box" hidden>
<label for="ev-rep-count">重复次数(含第一次)</label>
<input class="input" type="number" min="1" max="2000" id="ev-rep-count" name="rep_count"
value="<?= (int)($rp['count'] ?: 10) ?>">
<?php echo $evRowOpen(); ?>
<?= $evLab('重复次数(含第一次)', 'ev-rep-count') ?>
<div id="ev-rep-count-box" hidden>
<input class="input" type="number" min="1" max="2000" id="ev-rep-count" name="rep_count"
value="<?= (int)($rp['count'] ?: 10) ?>">
</div>
</div>
<div class="field" id="ev-rep-until-box" hidden>
<label for="ev-rep-until">重复到</label>
<input class="input" type="date" id="ev-rep-until" name="rep_until"
value="<?= e((string)($rp['until'] ?? '')) ?>">
<?php echo $evRowOpen(); ?>
<?= $evLab('重复到', 'ev-rep-until') ?>
<div id="ev-rep-until-box" hidden>
<input class="input" type="date" id="ev-rep-until" name="rep_until"
value="<?= e((string)($rp['until'] ?? '')) ?>">
</div>
</div>
<div class="field">
<label for="ev-status">状态</label>
<?php echo $evRowOpen(); ?>
<?= $evLab('状态', 'ev-status') ?>
<select class="select" id="ev-status" name="status">
<?php $st = (string)($evF['status'] ?? 'confirmed'); ?>
<option value="confirmed"<?= $st === 'confirmed' ? ' selected' : '' ?>>确定</option>
@@ -150,8 +193,8 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
</select>
</div>
<div class="field">
<label for="ev-transp">这段时间算</label>
<?php echo $evRowOpen(); ?>
<?= $evLab('这段时间算', 'ev-transp') ?>
<select class="select" id="ev-transp" name="transp">
<?php $tp = (int)($evF['transp'] ?? 0); ?>
<option value="0"<?= $tp === 0 ? ' selected' : '' ?>>忙碌</option>
@@ -159,14 +202,14 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
</select>
</div>
<div class="field full">
<label for="ev-note">备注</label>
<?php echo $evRowOpen('full'); ?>
<?= $evLab('备注', 'ev-note') ?>
<textarea class="input" id="ev-note" name="note" rows="3"
placeholder="要带什么、跟谁一起…"><?= e((string)($evF['note'] ?? '')) ?></textarea>
</div>
<div class="field full">
<label>附件(可多选)</label>
<?php echo $evRowOpen('full'); ?>
<?= $evLab('附件(可多选)') ?>
<?php /* v24 第 21 批 第 7 条:统一成「点击或拖动进行上传」的框。
⚠️ 文件框的 id 保持 `ev-attach`(原来就叫这个)—— e2e 与部署断言里
有按 id 定位的写法,改名会把它们静默打成假红。 */
@@ -189,7 +232,7 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
sync();
/* ⚠️ v24 第 21 批 第 7 条:原来这里有一整段「结束时间显隐 + 跟着开始时间走」的 JS。
现在起止两端**始终可见**,并且由 Semi 的范围控件统一接管(一次选完两端),
现在起止两端**始终可见**,并且由范围控件统一接管(一次选完两端),
所以那段"同步两端"的逻辑整个删掉 —— 留着不但没用,还会和范围控件抢着改那两个
原生字段(两处都写 → 值互相覆盖)。范围控件选完会自己把两端分别写回原生字段
(见 app.js 的 onRange → writeDT),服务端契约不变。 */
@@ -233,4 +276,5 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
}
})();
</script>
<?php unset($evF, $evAllDayDefault, $evIsAllDay, $evFields, $evAllDay); ?>
<?php unset($evF, $evAllDayDefault, $evIsAllDay, $evFields, $evAllDay, $evMv, $evMvStyle,
$evRowOpen, $evLab, $evLabelStar); ?>
+32 -7
View File
@@ -4,20 +4,31 @@
*
* ⚠️ 从月视图点**观影 / 旅程 / 任务**那些条会直接跳到对应模块(它们的"编辑页"在那里),
* 这里只处理 calendar_events 里真正属于某本日历的自定义日程。
*
* 🔴 v43(用户 2026-10-11 第 6 条):本模板多了一种用法 —— **`?embed=1` 只吐编辑表单**。
* 条目详情浮层里点「编辑」时用它(浮层把正文整块换成这张表单)。
* ⇒ `$evEmbed = true` 时:不输出页头、不输出只读 dl、不输出删除表单、
* 只保留那张编辑表单(外加已有附件)。
* ⚠️ 与 `days/edit` / `trips/node_edit` / `movies/edit` 的 `?embed=1` 同一套约定。
*/
$canWrite = \App\Auth::canWrite() && !empty($ev['_can_write']);
$evEmbed = !empty($evEmbed);
$ym = substr((string)$ev['starts_at'], 0, 7);
?>
<?php if (!$evEmbed): ?>
<div class="page-head">
<h1><?= e((string)$ev['title']) ?></h1>
<span class="sub"><?= e((string)($ev['starts_at'] ?? '')) ?></span>
<div class="actions">
<a class="btn btn-ghost" href="<?= e(url('calendar', ['ym' => substr((string)$ev['starts_at'], 0, 7)])) ?>">返回日程</a>
<a class="btn btn-ghost" href="<?= e(url('calendar', ['ym' => $ym])) ?>">返回日程</a>
</div>
</div>
<?php endif; ?>
<div class="card card-pad">
<?php $ym = substr((string)$ev['starts_at'], 0, 7); ?>
<?php if (!$canWrite): ?>
<div class="<?= $evEmbed ? '' : 'card card-pad' ?>">
<?php if ($evEmbed && !$canWrite): ?>
<p class="empty-hint">这本日历你只有只读权限,不能改。</p>
<?php elseif (!$canWrite): ?>
<dl class="kv">
<div class="kv-row"><dt>开始</dt>
<dd><?= e(empty($ev['all_day']) ? (string)$ev['starts_at'] : substr((string)$ev['starts_at'], 0, 10)) ?></dd>
@@ -60,7 +71,8 @@ $canWrite = \App\Auth::canWrite() && !empty($ev['_can_write']);
</dl>
<p class="empty-hint">这本日历你只有只读权限,看得到但不能改。</p>
<?php else: ?>
<form method="post" action="<?= e(url('calendar/event_update')) ?>" enctype="multipart/form-data">
<form method="post" action="<?= e(url('calendar/event_update')) ?>" enctype="multipart/form-data"
id="calEditForm">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= (int)$ev['id'] ?>">
<input type="hidden" name="ym" value="<?= e($ym) ?>">
@@ -95,12 +107,22 @@ $canWrite = \App\Auth::canWrite() && !empty($ev['_can_write']);
<?php endif; ?>
<div class="form-grid">
<?php $evFields = $ev; $evAllDay = false; require __DIR__ . '/_event_fields.php'; ?>
<?php /* 🔴 v43(用户第 5 条要求"观影式排版"):浮层里编辑时也用 `.mv-grid`,
与"新建日程"那个弹框一致(同一个字段块传 $evMvStyle 即可)。
⚠️ 整页形态(老链接直达)保持原来的 `.form-grid` 两列 ——
那是"页面表单"的排版习惯,不该跟着浮层一起变。 */
$evFields = $ev; $evAllDay = false;
if ($evEmbed) { $evMvStyle = true; echo '<div class="mv-grid">'; }
require __DIR__ . '/_event_fields.php';
if ($evEmbed) { echo '</div>'; unset($evMvStyle); }
?>
</div>
<?php if (!$evEmbed): ?>
<div class="btn-row">
<button class="btn" type="submit">保存</button>
<a class="btn btn-ghost" href="<?= e(url('calendar', ['ym' => $ym])) ?>">取消</a>
</div>
<?php endif; ?>
</form>
<?php $as = \App\Attachment::list(\App\Calendar::ATTACH, (int)$ev['id']); ?>
<?php if ($as): ?>
@@ -134,7 +156,9 @@ $canWrite = \App\Auth::canWrite() && !empty($ev['_can_write']);
*/
$evIsRepeat = trim((string)($ev['rrule'] ?? '')) !== '';
?>
<?php if ($evIsRepeat && !empty($occ)): ?>
<?php if ($evEmbed): ?>
<?php /* 浮层形态:删除留给详情页(那里有重复日程的三档删除),这里不出。 */ ?>
<?php elseif ($evIsRepeat && !empty($occ)): ?>
<form method="post" action="<?= e(url('calendar/event_del')) ?>" class="ev-del ev-del-scope"
onsubmit="return confirm('按上面选的范围删除?')">
<?= csrf_field() ?>
@@ -171,3 +195,4 @@ $canWrite = \App\Auth::canWrite() && !empty($ev['_can_write']);
<?php endif; ?>
<?php endif; ?>
</div>
<?php unset($evEmbed, $evIsRepeat, $evIsAllDay, $evAllDay, $evFields, $evMvStyle, $ym, $occ); ?>
+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;