From c50bc7de2be5b79ebee8c55e7b224c838c496d01 Mon Sep 17 00:00:00 2001 From: laoyang Date: Wed, 23 Sep 2026 15:18:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B4=BB=E5=8A=A8=E9=A1=B5=EF=BC=9A=E6=AF=8F?= =?UTF-8?q?=E4=B8=AA=E8=8A=82=E7=82=B9=E5=8D=A1=E7=89=87=E5=BA=95=E9=83=A8?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E5=8F=82=E4=B8=8E=E4=BA=BA=EF=BC=88=E5=A4=B4?= =?UTF-8?q?=E5=83=8F=20+=20=E5=90=8D=E5=AD=97=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前节点卡片上只有「👥 N」计数,要看是谁参加得点进节点详情。 现在卡片底部直接列出参与人:头像(缺头像用首字母兜底)+ 名字, 有心情顺带一个小 emoji;点任一人跳到该节点的参与区。 数据侧 Members::forNodes() 一条 IN 查询带回全部节点的参与人 (与 attachCounts 同样的防 N+1 思路),计划列与执行列共用。 无参与人的节点不渲染这一行。 已验证:本地全链 11 步全绿(渲染检查 292 项、表单交互 73 项); 远端 e2e 299 项 0 失败(新增活动页卡片参与人行专项断言); 部署后双 PHP 渲染逐字节一致、60 文件哈希一致。 --- assets/style.css | 33 ++++++++++++++++++++++++++ src/Controllers/ActivityController.php | 8 +++++++ src/Members.php | 23 ++++++++++++++++++ templates/activities/_node_card.php | 28 +++++++++++++++++++++- 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/assets/style.css b/assets/style.css index b5cf740..7611c63 100644 --- a/assets/style.css +++ b/assets/style.css @@ -1159,3 +1159,36 @@ input[type="date"]::-webkit-calendar-picker-indicator { cursor: pointer; padding .lightbox-next { right: 8px; } .lightbox-img { max-height: 66vh; } } + +/* ── 18. 活动页节点卡片:参与人(头像 + 名字) ────────────────────────────── */ +.node-members { + display: flex; flex-wrap: wrap; gap: 5px; + margin-top: 8px; padding-top: 8px; + border-top: 1px dashed var(--border); +} +.node-member-chip { + display: inline-flex; align-items: center; gap: 5px; + padding: 2px 9px 2px 3px; + background: var(--bg-soft); + border: 1px solid var(--border); + border-radius: 999px; + font-size: 12px; color: var(--text-soft); + text-decoration: none; + max-width: 100%; +} +.node-member-chip:hover { border-color: var(--accent); color: var(--accent); } +.node-member-chip img, +.node-member-avatar-fallback { + width: 20px; height: 20px; + border-radius: 50%; + object-fit: cover; + flex: 0 0 auto; +} +.node-member-avatar-fallback { + display: inline-flex; align-items: center; justify-content: center; + background: var(--accent-soft, var(--bg-soft)); + color: var(--accent); + font-size: 11px; font-weight: 650; +} +.node-member-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.node-member-emotion { font-size: 13px; line-height: 1; } diff --git a/src/Controllers/ActivityController.php b/src/Controllers/ActivityController.php index 7a64f1c..2201f72 100644 --- a/src/Controllers/ActivityController.php +++ b/src/Controllers/ActivityController.php @@ -8,6 +8,7 @@ use App\Auth; use App\Comments; use App\Database; use App\Expenses; +use App\Members; use App\Mime; class ActivityController @@ -214,11 +215,18 @@ class ActivityController $stmt->execute([$id]); $executedPlanIds = array_map('intval', $stmt->fetchAll(\PDO::FETCH_COLUMN)); + // 每个节点的参与人(头像 / 姓名),渲染在节点卡片底部 —— 一条 IN 查询,不 N+1 + $nodeMembers = Members::forNodes(array_merge( + array_column($planNodes, 'id'), + array_column($realityNodes, 'id') + )); + view('activities/show', [ 'activity' => $activity, 'planNodes' => $planNodes, 'realityNodes' => $realityNodes, 'executedPlanIds' => $executedPlanIds, + 'nodeMembers' => $nodeMembers, 'expenseSummary' => Expenses::summaryForActivity($id), 'commentTree' => Comments::tree(Comments::TARGET_ACTIVITY, $id), 'pageTitle' => $activity['title'], diff --git a/src/Members.php b/src/Members.php index 8919445..0a5cf31 100644 --- a/src/Members.php +++ b/src/Members.php @@ -84,6 +84,29 @@ class Members return $out; } + /** + * 一次查完多个节点的参与人明细(头像 / 姓名 / 情绪),供活动页的节点卡片使用。 + * 一条 IN 查询带回全部节点,避免每张卡片单独查(N+1)。 + * @return array> node_id => 参与人列表(按 node_members.id ASC) + */ + public static function forNodes(array $nodeIds): array + { + if (empty($nodeIds)) return []; + $ph = implode(',', array_fill(0, count($nodeIds), '?')); + $stmt = Database::get()->prepare(' + SELECT nm.trip_node_id, nm.user_id, nm.emotion, u.name, u.avatar + FROM node_members nm + LEFT JOIN users u ON u.id = nm.user_id + WHERE nm.trip_node_id IN (' . $ph . ') + ORDER BY nm.id ASC'); + $stmt->execute(array_values($nodeIds)); + $out = []; + foreach ($stmt->fetchAll() as $row) { + $out[(int)$row['trip_node_id']][] = $row; + } + return $out; + } + /** * 新增或更新一个参与人(同一节点同一人只留一条) * @return array{ok: bool, error?: string} diff --git a/templates/activities/_node_card.php b/templates/activities/_node_card.php index aa1078e..c7b997d 100644 --- a/templates/activities/_node_card.php +++ b/templates/activities/_node_card.php @@ -1,10 +1,12 @@ 参与人列表,ActivityController::show 注入;可缺省) */ $cardCategory = $cardCategory ?? 'plan'; $executedPlanIds = $executedPlanIds ?? []; +$nodeMembersMap = $nodeMembers ?? []; $statusLabels = [ 'planned' => '待执行', @@ -72,6 +74,30 @@ if ($cardCategory === 'plan') {
取消原因:
+ + + + + +