|
|
<?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\StudyTour;
|
|
|
use App\Models\Venue;
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
|
class H5ContentController extends Controller
|
|
|
{
|
|
|
public function activities(Request $request): JsonResponse
|
|
|
{
|
|
|
$size = max(1, min(30, (int) $request->input('page_size', 10)));
|
|
|
$rows = Activity::query()
|
|
|
->with('venue:id,name,lat,lng')
|
|
|
->where('is_active', true)
|
|
|
->when($request->filled('keyword'), function ($q) use ($request) {
|
|
|
$keyword = trim((string) $request->input('keyword'));
|
|
|
$q->where('title', 'like', "%{$keyword}%");
|
|
|
})
|
|
|
->orderForH5Listing()
|
|
|
->paginate($size);
|
|
|
|
|
|
$rows->getCollection()->transform(function ($a) {
|
|
|
return [
|
|
|
'id' => $a->id,
|
|
|
'title' => $a->title,
|
|
|
'summary' => $a->summary,
|
|
|
'image' => $a->cover_image,
|
|
|
'venue_name' => $a->venue?->name,
|
|
|
'address' => $a->address,
|
|
|
'lat' => $a->lat,
|
|
|
'lng' => $a->lng,
|
|
|
'venue_lat' => $a->venue?->lat,
|
|
|
'venue_lng' => $a->venue?->lng,
|
|
|
'start_at' => optional($a->start_at)?->toIso8601String(),
|
|
|
'end_at' => optional($a->end_at)?->toIso8601String(),
|
|
|
'registered_count' => (int) ($a->registered_count ?? 0),
|
|
|
'tags' => array_values($a->tags ?? []),
|
|
|
];
|
|
|
});
|
|
|
|
|
|
return response()->json($rows);
|
|
|
}
|
|
|
|
|
|
public function activityDetail(int $id): JsonResponse
|
|
|
{
|
|
|
$a = Activity::query()
|
|
|
->with([
|
|
|
'venue:id,name,address,lat,lng,open_time,district,venue_type,ticket_type,unit_name',
|
|
|
'activityDays',
|
|
|
])
|
|
|
->where('is_active', true)
|
|
|
->findOrFail($id);
|
|
|
|
|
|
$isBookable = $a->activityDays->contains(
|
|
|
fn (ActivityDay $d) => $d->isCurrentlyBookable()
|
|
|
);
|
|
|
|
|
|
return response()->json([
|
|
|
'id' => $a->id,
|
|
|
'title' => $a->title,
|
|
|
'summary' => $a->summary,
|
|
|
'detail_html' => $a->detail_html,
|
|
|
'image' => $a->cover_image,
|
|
|
'gallery_media' => $a->gallery_media ?? [],
|
|
|
'carousel' => $this->buildGalleryCarousel($a),
|
|
|
'venue' => $a->venue,
|
|
|
'address' => $a->address,
|
|
|
'lat' => $a->lat,
|
|
|
'lng' => $a->lng,
|
|
|
'start_at' => optional($a->start_at)?->toIso8601String(),
|
|
|
'end_at' => optional($a->end_at)?->toIso8601String(),
|
|
|
'registered_count' => (int) ($a->registered_count ?? 0),
|
|
|
'tags' => array_values($a->tags ?? []),
|
|
|
'reservation_notice' => $a->reservation_notice,
|
|
|
'is_bookable' => $isBookable,
|
|
|
'venue_type_color' => $this->resolveVenueTypeColor($a->venue?->venue_type),
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
public function venues(): JsonResponse
|
|
|
{
|
|
|
$rows = Venue::query()
|
|
|
->where('is_active', true)
|
|
|
->orderBy('sort')
|
|
|
->orderByDesc('id')
|
|
|
->get(['id', 'name', 'district', 'address', 'lat', 'lng', 'cover_image', 'open_time']);
|
|
|
|
|
|
return response()->json($rows);
|
|
|
}
|
|
|
|
|
|
public function venueDetail(int $id): JsonResponse
|
|
|
{
|
|
|
$v = Venue::query()->find($id);
|
|
|
if ($v === null) {
|
|
|
return response()->json(['message' => '场馆不存在'], 404);
|
|
|
}
|
|
|
if (! $v->is_active) {
|
|
|
return response()->json(['message' => '场馆已下架'], 404);
|
|
|
}
|
|
|
|
|
|
$payload = $v->toArray();
|
|
|
$payload['carousel'] = $this->buildGalleryCarousel($v);
|
|
|
$payload['live_people_count'] = (int) ($v->live_people_count ?? 0);
|
|
|
$payload['venue_type_color'] = $this->resolveVenueTypeColor($v->venue_type);
|
|
|
|
|
|
return response()->json($payload);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 轮播素材:gallery_media(可含图片/视频);为空时用封面图兜底。
|
|
|
*
|
|
|
* @return array<int, array{type: string, url: string}>
|
|
|
*/
|
|
|
private function buildGalleryCarousel(Venue|Activity $model): array
|
|
|
{
|
|
|
$items = [];
|
|
|
$seen = [];
|
|
|
|
|
|
foreach ($model->gallery_media ?? [] as $m) {
|
|
|
if (! is_array($m)) {
|
|
|
continue;
|
|
|
}
|
|
|
$url = trim((string) ($m['url'] ?? ''));
|
|
|
if ($url === '' || isset($seen[$url])) {
|
|
|
continue;
|
|
|
}
|
|
|
$type = $m['type'] ?? 'image';
|
|
|
if (! in_array($type, ['image', 'video'], true)) {
|
|
|
$type = 'image';
|
|
|
}
|
|
|
$seen[$url] = true;
|
|
|
$items[] = ['type' => $type, 'url' => $url];
|
|
|
}
|
|
|
|
|
|
if ($items === [] && $model->cover_image) {
|
|
|
$url = trim((string) $model->cover_image);
|
|
|
if ($url !== '') {
|
|
|
$items[] = ['type' => 'image', 'url' => $url];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $items;
|
|
|
}
|
|
|
|
|
|
/** 与首页地图场馆一致:字典 venue_type 的 item_remark 为色值 */
|
|
|
private function resolveVenueTypeColor(?string $venueType): string
|
|
|
{
|
|
|
if ($venueType === null || $venueType === '') {
|
|
|
return '#05c9ac';
|
|
|
}
|
|
|
|
|
|
$raw = DictItem::query()
|
|
|
->where('dict_type', 'venue_type')
|
|
|
->where('is_active', true)
|
|
|
->where('item_value', $venueType)
|
|
|
->value('item_remark');
|
|
|
|
|
|
$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 $color;
|
|
|
}
|
|
|
|
|
|
public function studyTourDetail(int $id): JsonResponse
|
|
|
{
|
|
|
$row = StudyTour::query()->where('is_active', true)->findOrFail($id);
|
|
|
$venueIds = collect($row->venue_ids ?? [])->filter()->values();
|
|
|
$venueMap = Venue::query()
|
|
|
->whereIn('id', $venueIds->all())
|
|
|
->get(['id', 'name', 'district', 'address', 'cover_image', 'lat', 'lng'])
|
|
|
->keyBy('id');
|
|
|
$venues = $venueIds->map(fn ($id) => $venueMap->get($id))->filter()->values();
|
|
|
|
|
|
return response()->json([
|
|
|
'id' => $row->id,
|
|
|
'name' => $row->name,
|
|
|
'tags' => array_values($row->tags ?? []),
|
|
|
'intro_html' => $row->intro_html,
|
|
|
'venues' => $venues,
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
public function studyTours(): JsonResponse
|
|
|
{
|
|
|
$rows = StudyTour::query()
|
|
|
->where('is_active', true)
|
|
|
->orderBy('sort')
|
|
|
->orderByDesc('id')
|
|
|
->get(['id', 'name', 'tags', 'venue_ids', 'intro_html']);
|
|
|
|
|
|
$venueIds = $rows->pluck('venue_ids')->flatten()->filter()->values()->all();
|
|
|
$venueMap = Venue::query()
|
|
|
->whereIn('id', $venueIds)
|
|
|
->get(['id', 'name', 'district', 'address', 'cover_image', 'lat', 'lng'])
|
|
|
->keyBy('id');
|
|
|
|
|
|
$payload = $rows->map(function ($row) use ($venueMap) {
|
|
|
$ids = collect($row->venue_ids ?? [])->filter()->values();
|
|
|
$firstVenue = $ids->isNotEmpty() ? $venueMap->get($ids->first()) : null;
|
|
|
$venueNames = $ids->map(fn ($id) => $venueMap->get($id)?->name)->filter()->values();
|
|
|
$cover = $ids->map(fn ($id) => $venueMap->get($id)?->cover_image)->filter()->first();
|
|
|
|
|
|
return [
|
|
|
'id' => $row->id,
|
|
|
'name' => $row->name,
|
|
|
'tags' => array_values($row->tags ?? []),
|
|
|
'venue_names' => $venueNames->all(),
|
|
|
'cover_image' => $cover ?: $firstVenue?->cover_image,
|
|
|
'first_address' => $firstVenue?->address,
|
|
|
'first_district' => $firstVenue?->district,
|
|
|
'venue_count' => $ids->count(),
|
|
|
];
|
|
|
})->values();
|
|
|
|
|
|
return response()->json($payload);
|
|
|
}
|
|
|
|
|
|
public function venueDicts(): JsonResponse
|
|
|
{
|
|
|
return response()->json([
|
|
|
'district' => DictItem::activeOptions('district'),
|
|
|
'venue_type' => DictItem::activeOptions('venue_type'),
|
|
|
'ticket_type' => DictItem::activeOptions('ticket_type'),
|
|
|
]);
|
|
|
}
|
|
|
}
|