diff --git a/app/HomeCards.php b/app/HomeCards.php index 558e569..0f2ab37 100644 --- a/app/HomeCards.php +++ b/app/HomeCards.php @@ -73,6 +73,15 @@ final class HomeCards 'name' => '今天的日程', 'desc' => '按日视图那样排的今天', 'w' => 4, 'h' => 8, 'route' => 'calendar', 'more' => '更多日程', 'n' => 0, 'mod' => 'calendar', + /* 🔴 v82(用户 2026-10-23):「日程,没有新建日程按钮……」 + ⚠️ 实测确认:他说的是**这张卡片**上没按钮 —— 日程模块页右上角 + 那颗 `#calAddBtn` 一直在、也可见(`l=1042, w=90`,点得到)。 + ⇒ 这里补上 `add`,卡片右上角就多一颗「新建日程」。 + ⚠️ `add_modal` 的 id 必须与 `templates/calendar/_add_modal.php` 里的 + 容器 id **逐字相同**(`calAddCard`)——写错的后果是"点了没反应", + 是本项目最难查的一类 bug(v81 真事故:`anAddCard` vs `anNewCard`)。 */ + 'add' => '新建日程', + 'add_modal' => 'calAddCard', ], 'tasks' => [ 'name' => '近期任务', 'desc' => '进行中与待办(含看板与状态)', @@ -136,6 +145,16 @@ final class HomeCards 'name' => '联系人', 'desc' => '名册里的人,可以选显示几个', 'w' => 4, 'h' => 4, 'route' => 'people', 'more' => '名册', 'n' => 8, 'mod' => 'people', + /* 🔴 v82(用户 2026-10-23):「名册,也没有新增成员这个按钮。」 + ⚠️ 实测确认:名册模块页那颗「添加成员」**一直在** + (``,可见可点)。 + 用户说的是**这张联系人卡片**上没有按钮。 + ⇒ 补 `add`,卡片右上角多一颗「新增成员」,点开的是**与名册页同一张** + 表单(`templates/people/form.php`,加了弹框形态)。 + ⚠️ `add_modal` 必须与 `people/form.php` 里那个容器 id 逐字相同 + (`pfAddCard`)。写错 = 点了没反应(v81 真事故:anAddCard vs anNewCard)。 */ + 'add' => '新增成员', + 'add_modal' => 'pfAddCard', ], ]; diff --git a/assets/css/app.css b/assets/css/app.css index 065add3..b7f43a7 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -4258,12 +4258,13 @@ button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-b z-index: 4; /* 在日程块(z-index 2)之上,要看得见 */ pointer-events: none; } -/* 左端那个小圆点:让"这条线是活的、代表此刻"更明显(也与日历 App 的通行做法一致) */ -.tg-now::before { - content: ""; position: absolute; left: -4px; top: -5px; - width: 8px; height: 8px; border-radius: 50%; background: #E0453B; - box-shadow: 0 0 0 2px rgba(255, 255, 255, .9); -} +/* 🔴 v82(用户 2026-10-23):「在日程模块中,那个红线左侧是不是还有一个红点? + 如果有,去掉。」—— 确实有,就是这条 `::before`。 + ⚠️ 首页日程那条红线 v44 已经删过一次红点(`.ag-now-dot`),用户当时说 + 「红点显示也不全,也不太好看。直接把红点去掉就行了,直接一根红线就可以了。」 + ⇒ 两处口径统一:**只有一根线,没有任何端点装饰**。 + ⚠️ 这条规则整条删掉,别只把 `content` 清空 —— 留个空规则在这儿, + 将来一定有人照着它把圆点"补回来"。 */ /* 过午夜(或跨天翻页)时线要能"走出去",不要卡在格子边界上闪 */ .tg-col { overflow: hidden; } @@ -4772,9 +4773,15 @@ button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-b 🔴 v44(用户第 3 条):左端那颗小圆点**删掉**了 —— 用户:「这个红点显示也不全,也不太好看。直接把红点去掉就行了,直接一根红线就可以了。」 ⇒ 直接一根 2px 红线;`.ag-now-dot` 这条规则随之删除(不再有那个元素)。 - 🔴 v80:左端改吃 `--ag-gutter`(与车道同一起点),右边留 2px 与色块对齐 —— - 原先是 `left: 46px; right: 0`,比车道多出 4px,红线尾巴会探出色块右沿。 */ -.ag-now { position: absolute; left: var(--ag-gutter, 50px); right: 2px; height: 0; border-top: 2px solid var(--red); z-index: 2; } + 🔴🔴 v82(用户 2026-10-23):「首页上的日程,红色线不对,变短了左侧缺了一块。」 + 真根因 = **双重缩进**:v80 把色块挪进 `.ag-lanes`(这一层自己已经 + `left: var(--ag-gutter)` 让出了刻度),红线在它**内部**却又写了一次 + `left: var(--ag-gutter, 50px)` ⇒ 实际左沿 = 刻度宽 + 刻度宽 = 100px, + 比车道左沿多出整整一份刻度宽(实测差 50.00px,正是 `--ag-gutter` 的值)。 + ⇒ 红线在车道内部,`left` 就必须是 `0`(相对车道),不能再吃 gutter。 + ⚠️ 别再"顺手写回去":`.ag-ev` 的百分比也在这层里算,只要给它们加过 gutter, + 就是同一个 bug 再长一遍。外来元素要判断"我在谁里面"再决定 left。 */ +.ag-now { position: absolute; left: 0; right: 0; height: 0; border-top: 2px solid var(--red); z-index: 2; } /* 日程块 */ /* 🔴 v30 第 2 条:「首页上的今天的日程,用色块表示,和日程保持一致。」 ⇒ 从原来的"白底卡片 + 左边 3px 色线"改成**与日历页 `.tg-block` 同款的实心色块**: diff --git a/index.php b/index.php index e01230e..7727f39 100644 --- a/index.php +++ b/index.php @@ -965,6 +965,32 @@ switch ($r) { 'kindsFull' => Anniversary::kindsFull($familyId), 'people' => Person::roster($familyId, $me, 'member', ''), ]; + } elseif ($hcKey === 'agenda' && !isset($homeAddModals['agenda'])) { + /* 🔴 v82(用户 2026-10-23):「日程,没有新建日程按钮」—— + 首页「今天的日程」卡片要能就地新建日程。 + ⚠️ 这里给的 `writable` 必须与 `calendar/index.php` 算的**同一口径**: + `Calendar::mine()` 取我可见的本 → 再按 `Calendar::canWrite()` 过一道。 + 少走 canWrite 的话,只读共享本会出现在下拉里,选了就被服务端拒。 + ⚠️ 顺手 `ensurePersonalBook()`:与日程页同一条幂等补本 —— + 老用户库里的"我自己的日程本"是 `seedDefaults()` 之后才有的, + 不补的话首页这张弹框的默认本可能落到**观影本**上, + 用户直接点"加上"就建出一条观影记录(v24 第 21 批真事故)。 */ + Calendar::ensurePersonalBook($familyId, $me); + $agCals = Calendar::mine($familyId, $me); + $homeAddModals['agenda'] = [ + 'writable' => array_values(array_filter( + $agCals, + static fn($c) => Calendar::canWrite($c, $me) + )), + ]; + } elseif ($hcKey === 'contacts' && !isset($homeAddModals['contacts'])) { + /* 🔴 v82(用户 2026-10-23):「名册,也没有新增成员这个按钮」—— + 首页「联系人」卡片要能就地新增成员。 + ⚠️ 只给标签候选(表单要用),**不给名册**:新增态不需要选人, + 白跑一次 roster 查询(1000+ 人)没有意义。 */ + $homeAddModals['contacts'] = [ + 'tagChoices' => PersonTag::choices($familyId, $me), + ]; } } } @@ -5727,6 +5753,16 @@ $want = array_keys($want); flash('err', '有附件没有保存:' . implode(';', $warns)); } flash('ok', '日程已加上。'); + /* 🔴 v82(用户 2026-10-23):「首页上的日程,……首页卡片上要有新建按钮」—— + 从**首页**那张弹框提交的,回首页(卡片上立刻能看到刚建的那条)。 + ⚠️ 默认仍回日程模块 —— 从日程页新建时,用户接着要看的是日历本身上 + 刚加的那一格,跳去首页反而看不到。 + ⚠️ 白名单取值(只认 'home'),绝不把 POST 字符串直接当跳转地址 + (开放重定向)—— 与 v81 的 `days/create`、`movies/create` 同一条约定。 + ⚠️ 放在 AJAX 分支**之后**:AJAX 那条路自己早 exit 了,走不到这里。 */ + if ((string)($_POST['back'] ?? '') === 'home') { + redirect('home'); + } redirect('calendar', ['ym' => substr($startsAt, 0, 7)]); break; diff --git a/templates/calendar/_add_modal.php b/templates/calendar/_add_modal.php new file mode 100644 index 0000000..4a507ea --- /dev/null +++ b/templates/calendar/_add_modal.php @@ -0,0 +1,284 @@ +日历本` / `$evMvStyle = true`)。 + * 片段抽走后这些串**只在这个新文件里**,而断言还在查 `calendar/index.php` + * ⇒ 正向断言当场中止部署、负向断言恒真没牙齿(v78 原话:"部署当场中止, + * 看着像断言写错,其实是目标文件漂移")。 + * ⇒ v82 已把这 5 条改成 `_cal_add_src` = 两个文件拼起来一起查。 + * + * ③ **变量由调用方给全**,片段自己不许偷宿主的变量(本项目重复踩过)。 + * 本片段需要的变量见文件末尾那段 `@var` 说明(含兜底逻辑)。 + * + * ───────────────────────────────────────────────────────────────────────── + * @var bool $calCanNew 能不能新建(`Auth::canWrite() && $writable` 非空);片段内自己再兜一次 + * @var array $calWritable 可写的日历本清单(含 id/name/color/kind) + * @var array $calCtx 上下文:ym / view / anchor / new_at / back / thisYm + * `back` 只认 'home' 或 ''('' = 回日程模块),服务端白名单取值 + */ +/* ---------------------------------------------------------------- 兜底 */ +/* 🔴🔴 v82 修正(2026-10-23):片段**自己读进来的变量一律加 `cal` 前缀**。 + --------------------------------------------------------------------------- + 第一版是照模块页的老变量名写的(`$ym / $view / $anchor / $newAt / $writable`), + 想着"沿用模块页的变量名最省事" —— 那是**整页**时代的写法,抽成片段就错了: + + · require 与宿主**共享一个作用域** ⇒ 片段里那句 + `$anchor = (string)($calCtx['anchor'] ?? date('Y-m-d'));` + 直接改写了**模块页自己的 `$anchor`**; + · 今天两者恰好同值(模块页就是把它自己的值传进 `$calCtx` 的), + 所以看不出问题 —— 但只要有一天首页传的 anchor 与模块页不同 + (首页固定"今天",模块页是页面锚点),模块页后半页就会静默错位: + 页头日期、翻页链接、时间网格的 `.tg-now` top 全跟着变。 + · `$newAt` / `$writable` 同理,其中 `$writable` 还被首页 unset 过, + 等于在首页里**把宿主的可写本清单干掉**(首页后面别的卡片要用)。 + + 这 5 个名字(anchor / newAt / writable / view / ym)都在 + `_tooling/_fb_fragment_scope_check.py` 的"宿主高频名"表里,是那个检查器 + 2026-10-03 就定下的规则(由来见它文件头:`unset($canWrite)` 把账本页炸成 + 一片 Warning,`php -l` 与 HTTP 200 全绿,只是页面排版坏了)。 + ⇒ 现在片段只碰 `$cal*`,宿主那套名字一个都不写。 + ⚠️ 别为了"读起来熟悉"改回去 —— 片段与宿主同名是这类 bug 的唯一入口。 */ +$calCanNew = (bool)($calCanNew ?? false); +$calWritable = (array)($calWritable ?? []); +if (!$calCanNew || !$calWritable) { + return; /* 没得写就整块不输出(与模块页那个 if 等价) */ +} +$calCtx = (array)($calCtx ?? []); +$calYm = (string)($calCtx['ym'] ?? date('Y-m')); +$calView = (string)($calCtx['view'] ?? 'month'); +$calAnchor = (string)($calCtx['anchor'] ?? date('Y-m-d')); +$calNewAt = (string)($calCtx['new_at'] ?? ''); +$calBack = (string)($calCtx['back'] ?? ''); +$calThisYm = date('Y-m'); +$calBooks = $calWritable; /* 片段内统一用这个短名遍历可写的本子 */ +/* 🔴 v82:通用弹框开关(`assets/js/add_modal.js`)认的是 `data-fb-add-modal`。 + ⚠️ 必须**按来源**决定挂不挂: + · 首页内嵌(`$calBack === 'home'`)→ 挂上,由 `add_modal.js` 管开合; + · 日程模块页 → **不挂**,那页有自己的一套内联 JS(`calAddBtn` 的 onclick + + 遮罩/✕/Esc 全在 `calendar/index.php` 底部),挂上会**绑两遍**: + 点一次开两次、关两次(`add_modal.js` 的注释里写着同一个坑)。 + ⇒ 判据用 `$calBack`(首页那条路才会传 'home'),不要用"页面里有没有别的弹框"。 */ +$fbAddModalAttr = ($calBack === 'home'); +?> + +
+ + `** —— `.cal-modal-panel` 被提前闭合、 + 跟着又被多关一层,于是 `
` 跑到了 + **面板外面**(DOM 上它是 `.cal-modal` 的直接子元素)。 + 面板 `max-height` 一卡,底栏就被挤到面板下方 → 用户看不到。 + 另有 CSS 一层:`.cal-modal-panel > form` 原先不是 flex 列, + `.cal-modal-body{flex:1 1 auto}` 因此不起作用(两处都已修)。 + ⚠️ 提交键用 `form="calAddForm"` **显式关联**:多一层/少一层 DOM 的 + 意外都不会再让它失效(它本来就在 form 内部,但显式写更稳)。 */ ?> + + +
+ +
+ + + - - -
- - `** —— `.cal-modal-panel` 被提前闭合、 - 跟着又被多关一层,于是 `
` 跑到了 - **面板外面**(DOM 上它是 `.cal-modal` 的直接子元素)。 - 面板 `max-height` 一卡,底栏就被挤到面板下方 → 用户看不到。 - 另有 CSS 一层:`.cal-modal-panel > form` 原先不是 flex 列, - `.cal-modal-body{flex:1 1 auto}` 因此不起作用(两处都已修)。 - ⚠️ 提交键用 `form="calAddForm"` **显式关联**:多一层/少一层 DOM 的 - 意外都不会再让它失效(它本来就在 form 内部,但显式写更稳)。 */ ?> - - -
- -
- - + 🔴 v82 再修(同日):片段内部现在已经**改用 `$cal*` 前缀**,不再覆盖 + 宿主的 `$anchor/$newAt/$writable/$view/$ym`(见该片段头部那段说明)。 + 所以这里连"片段残留物"都不必再清 —— 真正要清的只有本模板自己传进去的 + 三个 `$cal*` 入参。 */ + unset($calCanNew, $calWritable, $calCtx); + ?> diff --git a/templates/home.php b/templates/home.php index 6c7396e..e54f3db 100644 --- a/templates/home.php +++ b/templates/home.php @@ -286,6 +286,66 @@ require __DIR__ . '/days/form.php'; unset($anModalOnly, $anOpen, $anModal, $anEmbed, $anShell, $editing, $people, $kindsFull, $kinds); + } elseif ($hcAddKey === 'agenda') { + /* 🔴 v82(用户 2026-10-23):「日程,没有新建日程按钮……」 + ⇒ 首页「今天的日程」卡片右上角一颗「新建日程」,点开的是**与日程页同一张** + 弹框(`templates/calendar/_add_modal.php`,v82 从 calendar/index.php 抽出来的)。 + ⚠️ `$calCtx['back'] = 'home'` 是这个分支**唯一**与日程页不同的地方: + 提交完回首页(`calendar/event_add` 里认这个白名单值), + 用户不用离开首页就能看见刚建的那条。 + ⚠️ `ym`/`anchor` 用"今天"——首页卡片本来就只显示今天, + 新建的日程默认落在今天才符合直觉(日程页那边才用页面锚点)。 */ + $calCanNew = true; // 进到这里已经过了 Auth::canWrite() + $calWritable = (array)($hcAddData['writable'] ?? []); + $calCtx = [ + 'ym' => date('Y-m'), + 'view' => 'day', // 首页日程卡就是"日视图那样" + 'anchor' => date('Y-m-d'), + 'new_at' => '', + 'back' => 'home', + ]; + require __DIR__ . '/calendar/_add_modal.php'; + /* ⚠️ 🔴 v82 再修:这段 unset 清单**必须与片段真实赋过的名字一致**。 + 第一版抄的是模块页的老变量名(`$writable/$ym/$view/$anchor/$newAt`)—— + 那是片段改用 `$cal*` 前缀**之前**的清单,抄过来有两个问题: + ① 那五个名字**片段已经不再赋值**,unset 它们等于**误杀宿主** —— + 首页后面还有别的卡片在用(尤其 `$writable`); + ② 片段真正留下的 `$cal*` 反而没清干净。 + ⇒ 现在按片段的真实输出逐个清:`$calCanNew/$calWritable/$calCtx` + 三个入参 + 片段内部那 7 个 `$cal*` + `_event_fields.php` 那 8 个。 + ⚠️ 加新变量时**先回片段末尾那段清单对一遍**再往这里抄。 */ + unset($calCanNew, $calWritable, $calCtx, + $calYm, $calView, $calAnchor, $calNewAt, $calBack, $calThisYm, $calBooks, + $firstCal, $hasPersonal, $defDay, $defHM, $defEndH, $evFields, + $evAllDay, $evMvStyle, + /* ⚠️ 片段自己会设 `$fbAddModalAttr`(按 `$calBack` 判定); + 外层循环每轮也要设它,不清掉会让第二轮拿到上一轮的值。 */ + $fbAddModalAttr); + } elseif ($hcAddKey === 'contacts') { + /* 🔴 v82(用户 2026-10-23):「名册,也没有新增成员这个按钮。」 + ⇒ 首页「联系人」卡片右上角一颗「新增成员」,点开的是**与名册页同一张** + 表单(`templates/people/form.php` 新加的弹框形态)。 + ⚠️ 这套开关(`$pfModalOnly` / `$pfModal` / `$pfOpen`)是 v82 才加到 + people/form.php 的,之前那页只能整页打开 —— 所以这里**必须**逐个给全, + 漏一个就会退回"整页表单塞进卡片里"那种坏形态。 + ⚠️ `$person = null` + `$isEdit = false` 是新增态的判据(模板第一行按 + `$person['id']` 算 $isEdit),不要靠"不传"来默认。 */ + $pfModalOnly = true; + $pfModal = true; + $pfOpen = false; + $person = null; + $errors = []; + $canEditAll = true; + $isSelf = false; + $jobRows = [[]]; // 工作经历先给一行空白(与 people/new 一致) + $myNote = ''; + $myTags = []; + $tagChoices = (array)($hcAddData['tagChoices'] ?? []); + $myNick = ''; + $contacts = []; + require __DIR__ . '/people/form.php'; + unset($pfModalOnly, $pfModal, $pfOpen, $person, $errors, $canEditAll, $isSelf, + $jobRows, $myNote, $myTags, $tagChoices, $myNick, $contacts); } unset($fbAddModalAttr); ?> diff --git a/templates/people/form.php b/templates/people/form.php index 0eda297..a2f7cae 100644 --- a/templates/people/form.php +++ b/templates/people/form.php @@ -49,8 +49,47 @@ $ctHints = [ 'address' => '住址在「住址」一栏;这里放单位地址、老家地址等', 'url' => '主页、博客、店铺链接……', ]; + +/* ============================================================================ + 🔴 v82(用户 2026-10-23):「名册,也没有新增成员这个按钮。」 + ⚠️ 实测确认:名册模块页右上角那颗「添加成员」**一直在**(``,可见可点)。用户说的是**首页「联系人」卡片**上没有按钮。 + ⇒ 这张表单要能像 `movies/form.php` 那样被**别的页面内嵌成弹框**。 + --------------------------------------------------------------------------- + 两个开关(与观影表单同一套命名,别自创一套): + · `$pfModalOnly` 不输出 page-head(本模板被别的页面内嵌时用); + · `$pfModal` 输出弹框壳(`.cal-modal` + 遮罩)。默认:新增态就是弹框。 + 首页内嵌时由 `templates/home.php` 传 `$fbAddModalAttr = true`, + 本模板据此挂 `data-fb-add-modal` / `data-fb-add-close`(通用开关认这两个属性)。 + ⚠️ **表单契约一个字没改**:字段名 / enctype / action(people/create)全照旧。 + ============================================================================ */ +$pfModalOnly = !empty($pfModalOnly); +$pfModal = isset($pfModal) ? (bool)$pfModal : !$isEdit; +$pfOpen = isset($pfOpen) ? (bool)$pfOpen : ($pfModal && !$pfModalOnly); +$fbAdd = !empty($fbAddModalAttr); ?> + + + + + + +