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.

254 lines
10 KiB

3 weeks 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;
1 week ago
use App\Models\TicketGrabEvent;
3 weeks ago
use App\Models\Venue;
1 week ago
use App\Support\CalendarDateFormat;
use Carbon\Carbon;
3 weeks ago
use Illuminate\Http\JsonResponse;
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(),
3 weeks ago
'venue_total' => Venue::query()->visibleOnH5()->count(),
'activity_total' => Activity::query()->visibleOnH5()->count(),
1 week ago
/** 在馆实时总人数;接入真实客流前固定为 0勿用随机数 */
'in_venue_total' => 0,
3 weeks ago
];
$banners = Activity::query()
3 weeks ago
->visibleOnH5()
3 weeks 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();
1 week ago
/**
* 在馆 Top3接入按场馆真实在馆人数前不返回用「预约票量」汇总的替代数据避免与「在馆」混淆。
* 需恢复时:按 reservations 或客流表重查,见 git 历史。
*/
$topLiveVenues = [];
3 weeks ago
$venueTypeColors = DictItem::query()
->where('dict_type', 'venue_type')
->where('is_active', true)
->pluck('item_remark', 'item_value');
1 week ago
// 全部场馆列表(不过滤经纬度,用于列表展示)
$allVenues = Venue::query()
3 weeks ago
->visibleOnH5()
3 weeks ago
->orderBy('sort')
->orderByDesc('id')
3 weeks ago
->get()
3 weeks ago
->map(function ($v) use ($venueTypeColors) {
1 week ago
$p = $v->toArray();
3 weeks ago
$types = $p['venue_types'] ?? null;
$firstType = (is_array($types) && count($types)) ? (string) ($types[0] ?? '') : ($p['venue_type'] ?? '');
3 weeks ago
$raw = $venueTypeColors->get($firstType);
3 weeks 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 [
3 weeks ago
'id' => (int) $p['id'],
'name' => $p['name'],
2 weeks ago
'sort' => (int) ($p['sort'] ?? 0),
3 weeks ago
'district' => $p['district'],
'address' => $p['address'],
1 week ago
'lat' => $p['lat'] ? (float) $p['lat'] : null,
'lng' => $p['lng'] ? (float) $p['lng'] : null,
3 weeks ago
'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'],
1 week ago
'booking_mode' => $p['booking_mode'] ?? null,
2 weeks ago
'open_mode' => $p['open_mode'] ?? null,
1 week ago
'is_included_in_stats' => (bool) ($p['is_included_in_stats'] ?? false),
3 weeks ago
'venue_type_color' => $color,
];
})
->values();
1 week ago
// 地图场馆:只返回有经纬度的(用于地图标点)
$mapVenues = $allVenues->filter(function ($v) {
return $v['lat'] !== null && $v['lng'] !== null;
})->values();
1 week ago
$actRows = Activity::query()
3 weeks ago
->with('venue:id,name')
->with('activityDays')
3 weeks ago
->visibleOnH5()
3 weeks ago
->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 [
1 week ago
'list_kind' => 'activity',
3 weeks ago
'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(),
2 weeks ago
'schedule_status' => Activity::computeScheduleStatusFromBounds($a->start_at, $a->end_at),
3 weeks ago
'registered_count' => (int) ($a->registered_count ?? 0),
'is_bookable' => $isBookable,
'tags' => array_values($a->tags ?? []),
];
1 week ago
});
$tgRows = TicketGrabEvent::query()
->with('venues')
->visibleOnH5()
->get(['id', 'title', 'summary', 'cover_image', 'start_at', 'end_at', 'registered_count', 'address', 'tags', 'sort', 'booking_start_at', 'booking_end_at'])
->map(function ($e) {
$first = $e->venues->first();
$t = now()->toDateString();
$bookable = $e->booking_start_at && $e->booking_end_at
&& $t >= $e->booking_start_at->toDateString()
&& $t <= $e->booking_end_at->toDateString()
&& (! $e->end_at || $e->end_at->toDateString() >= $t);
return [
'list_kind' => 'ticket_grab',
'id' => $e->id,
'title' => $e->title,
'summary' => $e->summary,
'image' => $e->cover_image,
'venue_name' => $first?->name,
'address' => $e->address,
'start_at' => CalendarDateFormat::ymdFromDatetime($e->start_at),
'end_at' => CalendarDateFormat::ymdFromDatetime($e->end_at),
'schedule_status' => TicketGrabEvent::computeScheduleStatusFromBounds($e->start_at, $e->end_at),
'registered_count' => (int) ($e->registered_count ?? 0),
'is_bookable' => $bookable,
'can_grab_today' => $e->canGrabToday(),
'venue_count' => $e->venues->count(),
'tags' => array_values($e->tags ?? []),
];
});
$controller = $this;
$today = Carbon::now((string) config('app.timezone'))->toDateString();
$hotActivities = $actRows->merge($tgRows)->sort(function (array $x, array $y) use ($today, $controller) {
$sx = $controller->homeScheduleRank($x['start_at'] ?? null, $x['end_at'] ?? null, $today);
$sy = $controller->homeScheduleRank($y['start_at'] ?? null, $y['end_at'] ?? null, $today);
if ($sx !== $sy) {
return $sx <=> $sy;
}
$a = $x['start_at'] ?? '';
$b = $y['start_at'] ?? '';
if ($a !== $b) {
if ($a === '') {
return 1;
}
if ($b === '') {
return -1;
}
return strcmp((string) $a, (string) $b);
}
return (int) $y['id'] <=> (int) $x['id'];
})->values()->take(5)->values();
3 weeks ago
$rankings = $hotActivities->take(2)->values();
$activeStudyTours = StudyTour::query()
->where('is_active', true)
->orderBy('sort')
->orderByDesc('id')
->limit(3)
3 weeks ago
->get(['id', 'name', 'tags', 'venue_ids', 'intro_html', 'cover_image']);
3 weeks 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 weeks ago
$fallbackCover = $venueIds->map(fn ($id) => $venueMap->get($id)?->cover_image)->filter()->first();
$tourCover = trim((string) ($row->cover_image ?? ''));
3 weeks ago
return [
'id' => $row->id,
'name' => $row->name,
'tags' => array_values($row->tags ?? []),
'venue_names' => $venueNames,
3 weeks ago
'cover_image' => $tourCover !== '' ? $tourCover : $fallbackCover,
3 weeks ago
];
})->values();
return response()->json([
'stats' => $stats,
'banners' => $banners,
'top_live_venues' => $topLiveVenues,
1 week ago
'all_venues' => $allVenues,
3 weeks ago
'map_venues' => $mapVenues,
'rankings' => $rankings,
'hot_activities' => $hotActivities,
'study_tours' => $studyTours,
3 weeks ago
'venue_dicts' => [
'district' => DictItem::activeOptions('district'),
2 weeks ago
'venue_type' => DictItem::activeVenueTypeOptionsWithColor(),
3 weeks ago
'venue_appointment_type' => DictItem::activeOptions('venue_appointment_type'),
1 week ago
'venue_booking_mode' => DictItem::activeOptions('venue_booking_mode'),
2 weeks ago
'venue_open_mode' => DictItem::activeOptions('venue_open_mode'),
3 weeks ago
'ticket_type' => DictItem::activeOptions('ticket_type'),
],
3 weeks ago
]);
}
1 week ago
/**
* 与活动列表 H5 混合排序0 进行中/未结束, 1 未开始, 2 已结束.
*/
private function homeScheduleRank(?string $startIso, ?string $endIso, string $today): int
{
$s = $startIso ? Carbon::parse($startIso)->toDateString() : null;
$e = $endIso ? Carbon::parse($endIso)->toDateString() : null;
if (! $e && ! $s) {
return 0;
}
if ($e && $e < $today) {
return 2;
}
if ($s && $s > $today) {
return 1;
}
return 0;
}
3 weeks ago
}