Reservation::query()->where('status', '!=', 'cancelled')->count(), 'verified_total' => Reservation::query()->where('status', 'verified')->count(), 'venue_total' => Venue::query()->where('is_active', true)->count(), 'activity_total' => Activity::query()->where('is_active', true)->count(), ]; $banners = Activity::query() ->where('is_active', true) ->whereNotNull('cover_image') ->where('cover_image', '!=', '') ->orderBy('sort') ->orderByDesc('id') ->limit(5) ->get(['id', 'title', 'summary', 'cover_image']) ->map(fn ($a) => [ 'id' => $a->id, 'title' => $a->title, 'summary' => $a->summary, 'image' => $a->cover_image, ]) ->values(); $topLiveVenues = DB::table('reservations') ->join('venues', 'venues.id', '=', 'reservations.venue_id') ->where('reservations.status', '!=', 'cancelled') ->where('venues.is_active', true) ->select('venues.id', 'venues.name', DB::raw('SUM(COALESCE(reservations.ticket_count, 1)) as people_count')) ->groupBy('venues.id', 'venues.name') ->orderByDesc('people_count') ->limit(3) ->get() ->map(fn ($r) => [ 'id' => (int) $r->id, 'name' => $r->name, 'people_count' => (int) $r->people_count, ]) ->values(); $venueTypeColors = DictItem::query() ->where('dict_type', 'venue_type') ->where('is_active', true) ->pluck('item_remark', 'item_value'); // 地图场馆:一次性返回全部有效坐标点(体量约百级,无需分页) $mapVenues = Venue::query() ->where('is_active', true) ->whereNotNull('lat') ->whereNotNull('lng') ->orderBy('sort') ->orderByDesc('id') ->get(['id', 'name', 'district', 'address', 'lat', 'lng', 'cover_image', 'venue_type', 'ticket_type']) ->map(function ($v) use ($venueTypeColors) { $raw = $venueTypeColors->get($v->venue_type); $color = '#05c9ac'; if (is_string($raw) && trim($raw) !== '') { $t = trim($raw); if (! str_starts_with($t, '#')) { $t = '#'.$t; } if (preg_match('/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/', $t)) { $color = $t; } } return [ 'id' => $v->id, 'name' => $v->name, 'district' => $v->district, 'address' => $v->address, 'lat' => (float) $v->lat, 'lng' => (float) $v->lng, 'image' => $v->cover_image, 'venue_type' => $v->venue_type, 'ticket_type' => $v->ticket_type, 'venue_type_color' => $color, ]; }) ->values(); $hotActivities = Activity::query() ->with('venue:id,name') ->with('activityDays') ->where('is_active', true) ->orderForH5Listing() ->limit(5) ->get(['id', 'venue_id', 'title', 'summary', 'cover_image', 'start_at', 'end_at', 'registered_count', 'address', 'tags', 'sort']) ->map(function ($a) { $isBookable = $a->activityDays->contains( fn (ActivityDay $d) => $d->isCurrentlyBookable() ); return [ 'id' => $a->id, 'title' => $a->title, 'summary' => $a->summary, 'image' => $a->cover_image, 'venue_name' => $a->venue?->name, 'address' => $a->address, 'start_at' => optional($a->start_at)?->toIso8601String(), 'end_at' => optional($a->end_at)?->toIso8601String(), 'registered_count' => (int) ($a->registered_count ?? 0), 'is_bookable' => $isBookable, 'tags' => array_values($a->tags ?? []), ]; }) ->values(); $rankings = $hotActivities->take(2)->values(); $activeStudyTours = StudyTour::query() ->where('is_active', true) ->orderBy('sort') ->orderByDesc('id') ->limit(3) ->get(['id', 'name', 'tags', 'venue_ids', 'intro_html', 'cover_image']); $venueMap = Venue::query() ->whereIn('id', $activeStudyTours->pluck('venue_ids')->flatten()->filter()->values()->all()) ->get(['id', 'name', 'cover_image']) ->keyBy('id'); $studyTours = $activeStudyTours->map(function ($row) use ($venueMap) { $venueIds = collect($row->venue_ids ?? [])->values(); $venueNames = $venueIds->map(fn ($id) => $venueMap->get($id)?->name)->filter()->values(); $fallbackCover = $venueIds->map(fn ($id) => $venueMap->get($id)?->cover_image)->filter()->first(); $tourCover = trim((string) ($row->cover_image ?? '')); return [ 'id' => $row->id, 'name' => $row->name, 'tags' => array_values($row->tags ?? []), 'venue_names' => $venueNames, 'cover_image' => $tourCover !== '' ? $tourCover : $fallbackCover, ]; })->values(); return response()->json([ 'stats' => $stats, 'banners' => $banners, 'top_live_venues' => $topLiveVenues, 'map_venues' => $mapVenues, 'rankings' => $rankings, 'hot_activities' => $hotActivities, 'study_tours' => $studyTours, 'venue_dicts' => [ 'district' => DictItem::activeOptions('district'), 'venue_type' => DictItem::activeOptions('venue_type'), 'ticket_type' => DictItem::activeOptions('ticket_type'), ], ]); } }