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') {
取消原因:
+ + + +
+ + + + + + + + + + + + + +
+ +