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.

242 lines
8.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?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'),
]);
}
}