|
|
|
|
<?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(),
|
|
|
|
|
'venue_total' => Venue::query()->visibleOnH5()->count(),
|
|
|
|
|
'activity_total' => Activity::query()->visibleOnH5()->count(),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$banners = Activity::query()
|
|
|
|
|
->visibleOnH5()
|
|
|
|
|
->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)
|
|
|
|
|
->where('venues.audit_status', Venue::AUDIT_APPROVED)
|
|
|
|
|
->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()
|
|
|
|
|
->visibleOnH5()
|
|
|
|
|
->orderBy('sort')
|
|
|
|
|
->orderByDesc('id')
|
|
|
|
|
->get()
|
|
|
|
|
->map(function ($v) use ($venueTypeColors) {
|
|
|
|
|
$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'] ?? '');
|
|
|
|
|
$raw = $venueTypeColors->get($firstType);
|
|
|
|
|
$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' => (int) $p['id'],
|
|
|
|
|
'name' => $p['name'],
|
|
|
|
|
'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'],
|
|
|
|
|
'open_mode' => $p['open_mode'] ?? null,
|
|
|
|
|
'venue_type_color' => $color,
|
|
|
|
|
];
|
|
|
|
|
})
|
|
|
|
|
->filter()
|
|
|
|
|
->values();
|
|
|
|
|
|
|
|
|
|
$hotActivities = Activity::query()
|
|
|
|
|
->with('venue:id,name')
|
|
|
|
|
->with('activityDays')
|
|
|
|
|
->visibleOnH5()
|
|
|
|
|
->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(),
|
|
|
|
|
'schedule_status' => Activity::computeScheduleStatusFromBounds($a->start_at, $a->end_at),
|
|
|
|
|
'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'),
|
|
|
|
|
'venue_appointment_type' => DictItem::activeOptions('venue_appointment_type'),
|
|
|
|
|
'venue_open_mode' => DictItem::activeOptions('venue_open_mode'),
|
|
|
|
|
'ticket_type' => DictItem::activeOptions('ticket_type'),
|
|
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|