You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
7.6 KiB

3 days ago
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Activity;
use App\Models\ActivityDay;
use App\Models\DictItem;
use App\Models\Reservation;
use App\Models\StudyTour;
use App\Models\Venue;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
class H5HomeController extends Controller
{
public function index(): JsonResponse
{
$stats = [
'reservation_total' => Reservation::query()->where('status', '!=', 'cancelled')->count(),
'verified_total' => Reservation::query()->where('status', 'verified')->count(),
2 days ago
'venue_total' => Venue::query()->visibleOnH5()->count(),
'activity_total' => Activity::query()->visibleOnH5()->count(),
3 days ago
];
$banners = Activity::query()
2 days ago
->visibleOnH5()
3 days ago
->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)
2 days ago
->where('venues.audit_status', Venue::AUDIT_APPROVED)
3 days ago
->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()
2 days ago
->visibleOnH5()
3 days ago
->orderBy('sort')
->orderByDesc('id')
2 days ago
->get()
3 days ago
->map(function ($v) use ($venueTypeColors) {
2 days ago
$p = $v->toH5Payload();
if ($p === null) {
return null;
}
$lat = $p['lat'] ?? null;
$lng = $p['lng'] ?? null;
if ($lat === null || $lng === null || $lat === '' || $lng === '') {
return null;
}
$types = $p['venue_types'] ?? null;
$firstType = (is_array($types) && count($types)) ? (string) ($types[0] ?? '') : ($p['venue_type'] ?? '');
2 days ago
$raw = $venueTypeColors->get($firstType);
3 days ago
$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 [
2 days ago
'id' => (int) $p['id'],
'name' => $p['name'],
7 hours ago
'sort' => (int) ($p['sort'] ?? 0),
2 days ago
'district' => $p['district'],
'address' => $p['address'],
'lat' => (float) $lat,
'lng' => (float) $lng,
'image' => $p['cover_image'],
'venue_type' => $p['venue_type'],
'venue_types' => is_array($types) ? array_values($types) : [],
'ticket_type' => $p['ticket_type'],
'appointment_type' => $p['appointment_type'],
2 days ago
'open_mode' => $p['open_mode'] ?? null,
3 days ago
'venue_type_color' => $color,
];
})
2 days ago
->filter()
3 days ago
->values();
$hotActivities = Activity::query()
->with('venue:id,name')
->with('activityDays')
2 days ago
->visibleOnH5()
3 days ago
->orderForH5Listing()
3 days ago
->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(),
1 day ago
'schedule_status' => Activity::computeScheduleStatusFromBounds($a->start_at, $a->end_at),
3 days ago
'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)
3 days ago
->get(['id', 'name', 'tags', 'venue_ids', 'intro_html', 'cover_image']);
3 days ago
$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();
3 days ago
$fallbackCover = $venueIds->map(fn ($id) => $venueMap->get($id)?->cover_image)->filter()->first();
$tourCover = trim((string) ($row->cover_image ?? ''));
3 days ago
return [
'id' => $row->id,
'name' => $row->name,
'tags' => array_values($row->tags ?? []),
'venue_names' => $venueNames,
3 days ago
'cover_image' => $tourCover !== '' ? $tourCover : $fallbackCover,
3 days ago
];
})->values();
return response()->json([
'stats' => $stats,
'banners' => $banners,
'top_live_venues' => $topLiveVenues,
'map_venues' => $mapVenues,
'rankings' => $rankings,
'hot_activities' => $hotActivities,
'study_tours' => $studyTours,
3 days ago
'venue_dicts' => [
'district' => DictItem::activeOptions('district'),
7 hours ago
'venue_type' => DictItem::activeVenueTypeOptionsWithColor(),
2 days ago
'venue_appointment_type' => DictItem::activeOptions('venue_appointment_type'),
2 days ago
'venue_open_mode' => DictItem::activeOptions('venue_open_mode'),
3 days ago
'ticket_type' => DictItem::activeOptions('ticket_type'),
],
3 days ago
]);
}
}