|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Support;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Models\DictItem;
|
|
|
|
|
|
use App\Models\StudyTour;
|
|
|
|
|
|
use App\Models\Venue;
|
|
|
|
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
|
use Illuminate\Validation\Rule;
|
|
|
|
|
|
|
|
|
|
|
|
class StudyTourPayload
|
|
|
|
|
|
{
|
|
|
|
|
|
public static function validationRules(bool $creating = false): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$seasonRule = Rule::exists('dict_items', 'item_value')->where(
|
|
|
|
|
|
fn ($q) => $q->where('dict_type', 'study_tour_season')->where('is_active', true)
|
|
|
|
|
|
);
|
|
|
|
|
|
$gradeRule = Rule::exists('dict_items', 'item_value')->where(
|
|
|
|
|
|
fn ($q) => $q->where('dict_type', 'study_tour_grade_level')->where('is_active', true)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
$required = $creating ? 'required' : 'sometimes';
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'name' => [$required, 'string', 'max:120'],
|
|
|
|
|
|
'tags' => ['nullable', 'array'],
|
|
|
|
|
|
'tags.*' => ['string', 'max:50'],
|
|
|
|
|
|
'venue_items' => [$required, 'array', 'min:1'],
|
|
|
|
|
|
'venue_items.*.type' => ['required', 'in:system,custom'],
|
|
|
|
|
|
'venue_items.*.venue_id' => ['required_if:venue_items.*.type,system', 'nullable', 'integer', 'exists:venues,id'],
|
|
|
|
|
|
'venue_items.*.name' => ['required_if:venue_items.*.type,custom', 'nullable', 'string', 'max:120'],
|
|
|
|
|
|
'venue_items.*.address' => ['nullable', 'string', 'max:255'],
|
|
|
|
|
|
'venue_ids' => ['nullable', 'array'],
|
|
|
|
|
|
'venue_ids.*' => ['integer', 'exists:venues,id'],
|
|
|
|
|
|
'org_name' => ['nullable', 'string', 'max:200'],
|
|
|
|
|
|
'seasons' => ['nullable', 'array'],
|
|
|
|
|
|
'seasons.*' => ['string', 'max:50', $seasonRule],
|
|
|
|
|
|
'suitable_count' => ['nullable', 'string', 'max:50'],
|
|
|
|
|
|
'grade_levels' => ['nullable', 'array'],
|
|
|
|
|
|
'grade_levels.*' => ['string', 'max:50', $gradeRule],
|
|
|
|
|
|
'duration' => ['nullable', 'string', 'max:100'],
|
|
|
|
|
|
'contact_person' => ['nullable', 'string', 'max:50'],
|
|
|
|
|
|
'contact_phones' => ['nullable', 'string', 'max:200'],
|
|
|
|
|
|
'cover_image' => ['nullable', 'string', 'max:255'],
|
|
|
|
|
|
'gallery_media' => ['nullable', 'array'],
|
|
|
|
|
|
'gallery_media.*.type' => ['required_with:gallery_media', 'in:image,video'],
|
|
|
|
|
|
'gallery_media.*.url' => ['required_with:gallery_media', 'string', 'max:255'],
|
|
|
|
|
|
'intro_html' => ['nullable', 'string'],
|
|
|
|
|
|
'route_plans' => ['nullable', 'array'],
|
|
|
|
|
|
'route_plans.*.date_label' => ['nullable', 'string', 'max:120'],
|
|
|
|
|
|
'route_plans.*.items' => ['nullable', 'array'],
|
|
|
|
|
|
'route_plans.*.items.*.time' => ['nullable', 'string', 'max:80'],
|
|
|
|
|
|
'route_plans.*.items.*.activity' => ['nullable', 'string', 'max:500'],
|
|
|
|
|
|
'route_plans.*.items.*.location' => ['nullable', 'string', 'max:255'],
|
|
|
|
|
|
'courses' => ['nullable', 'array'],
|
|
|
|
|
|
'courses.*.sort' => ['nullable', 'integer', 'min:0'],
|
|
|
|
|
|
'courses.*.name' => ['nullable', 'string', 'max:200'],
|
|
|
|
|
|
'courses.*.content' => ['nullable', 'string', 'max:2000'],
|
|
|
|
|
|
'fee_html' => ['nullable', 'string'],
|
|
|
|
|
|
'implementation_html' => ['nullable', 'string'],
|
|
|
|
|
|
'sort' => ['nullable', 'integer', 'min:0'],
|
|
|
|
|
|
'is_on_shelf' => ['sometimes', 'boolean'],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function compactText(string $text): string
|
|
|
|
|
|
{
|
|
|
|
|
|
$text = trim($text);
|
|
|
|
|
|
if ($text === '') {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
$text = preg_replace('/[\x{00A0}\x{2002}\x{2003}\x{3000}]/u', ' ', $text) ?? $text;
|
|
|
|
|
|
$text = preg_replace('/\s+/u', ' ', $text) ?? $text;
|
|
|
|
|
|
|
|
|
|
|
|
return trim($text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function compactMultilineText(string $text): string
|
|
|
|
|
|
{
|
|
|
|
|
|
$text = trim($text);
|
|
|
|
|
|
if ($text === '') {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
$lines = preg_split('/\R/u', $text) ?: [];
|
|
|
|
|
|
$lines = array_values(array_filter(
|
|
|
|
|
|
array_map(fn ($line) => self::compactText((string) $line), $lines),
|
|
|
|
|
|
fn ($line) => $line !== ''
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
return implode("\n", $lines);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function normalizeIncoming(array $data): array
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (['name', 'org_name', 'suitable_count', 'duration', 'contact_person'] as $key) {
|
|
|
|
|
|
if (array_key_exists($key, $data) && is_string($data[$key])) {
|
|
|
|
|
|
$data[$key] = self::compactText($data[$key]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (array_key_exists('tags', $data)) {
|
|
|
|
|
|
$data['tags'] = array_values($data['tags'] ?? []);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (array_key_exists('seasons', $data)) {
|
|
|
|
|
|
$data['seasons'] = array_values($data['seasons'] ?? []);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (array_key_exists('grade_levels', $data)) {
|
|
|
|
|
|
$data['grade_levels'] = array_values($data['grade_levels'] ?? []);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (array_key_exists('venue_items', $data)) {
|
|
|
|
|
|
$data['venue_items'] = self::normalizeVenueItems($data['venue_items'] ?? []);
|
|
|
|
|
|
$data['venue_ids'] = self::venueIdsFromItems($data['venue_items']);
|
|
|
|
|
|
} elseif (array_key_exists('venue_ids', $data)) {
|
|
|
|
|
|
$ids = array_values($data['venue_ids'] ?? []);
|
|
|
|
|
|
$data['venue_ids'] = $ids;
|
|
|
|
|
|
$data['venue_items'] = self::venueItemsFromIds($ids);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (array_key_exists('route_plans', $data)) {
|
|
|
|
|
|
$data['route_plans'] = self::normalizeRoutePlans($data['route_plans'] ?? []);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (array_key_exists('courses', $data)) {
|
|
|
|
|
|
$data['courses'] = self::normalizeCourses($data['courses'] ?? []);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (array_key_exists('contact_phones', $data)) {
|
|
|
|
|
|
$data['contact_phones'] = self::normalizeContactPhones((string) ($data['contact_phones'] ?? ''));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (array_key_exists('gallery_media', $data)) {
|
|
|
|
|
|
$data['gallery_media'] = array_values($data['gallery_media'] ?? []);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function venueItemsForRecord(StudyTour $row): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$items = $row->venue_items;
|
|
|
|
|
|
if (is_array($items) && $items !== []) {
|
|
|
|
|
|
return self::normalizeVenueItems($items);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return self::venueItemsFromIds($row->venue_ids ?? []);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function normalizeVenueItems(array $items): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$out = [];
|
|
|
|
|
|
foreach ($items as $item) {
|
|
|
|
|
|
if (! is_array($item)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$type = (string) ($item['type'] ?? '');
|
|
|
|
|
|
if ($type === 'system') {
|
|
|
|
|
|
$venueId = (int) ($item['venue_id'] ?? 0);
|
|
|
|
|
|
if ($venueId > 0) {
|
|
|
|
|
|
$out[] = ['type' => 'system', 'venue_id' => $venueId];
|
|
|
|
|
|
}
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ($type === 'custom') {
|
|
|
|
|
|
$name = self::compactText((string) ($item['name'] ?? ''));
|
|
|
|
|
|
if ($name === '') {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$row = ['type' => 'custom', 'name' => $name];
|
|
|
|
|
|
$address = self::compactText((string) ($item['address'] ?? ''));
|
|
|
|
|
|
if ($address !== '') {
|
|
|
|
|
|
$row['address'] = $address;
|
|
|
|
|
|
}
|
|
|
|
|
|
$out[] = $row;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function venueItemsFromIds(array $ids): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return collect($ids)
|
|
|
|
|
|
->map(fn ($id) => (int) $id)
|
|
|
|
|
|
->filter(fn ($id) => $id > 0)
|
|
|
|
|
|
->unique()
|
|
|
|
|
|
->values()
|
|
|
|
|
|
->map(fn ($id) => ['type' => 'system', 'venue_id' => $id])
|
|
|
|
|
|
->all();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function venueIdsFromItems(array $items): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return collect(self::normalizeVenueItems($items))
|
|
|
|
|
|
->filter(fn ($item) => ($item['type'] ?? '') === 'system')
|
|
|
|
|
|
->map(fn ($item) => (int) ($item['venue_id'] ?? 0))
|
|
|
|
|
|
->filter(fn ($id) => $id > 0)
|
|
|
|
|
|
->values()
|
|
|
|
|
|
->all();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function normalizeRoutePlans(array $plans): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$out = [];
|
|
|
|
|
|
foreach ($plans as $plan) {
|
|
|
|
|
|
if (! is_array($plan)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$items = [];
|
|
|
|
|
|
foreach ($plan['items'] ?? [] as $item) {
|
|
|
|
|
|
if (! is_array($item)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$items[] = [
|
|
|
|
|
|
'time' => self::compactText((string) ($item['time'] ?? '')),
|
|
|
|
|
|
'activity' => self::compactText((string) ($item['activity'] ?? '')),
|
|
|
|
|
|
'location' => self::compactText((string) ($item['location'] ?? '')),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
$out[] = [
|
|
|
|
|
|
'date_label' => self::compactText((string) ($plan['date_label'] ?? '')),
|
|
|
|
|
|
'items' => $items,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function normalizeCourses(array $courses): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$out = [];
|
|
|
|
|
|
$sort = 1;
|
|
|
|
|
|
foreach ($courses as $course) {
|
|
|
|
|
|
if (! is_array($course)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$name = self::compactText((string) ($course['name'] ?? ''));
|
|
|
|
|
|
$content = self::compactText((string) ($course['content'] ?? ''));
|
|
|
|
|
|
if ($name === '' && $content === '') {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$out[] = [
|
|
|
|
|
|
'sort' => (int) ($course['sort'] ?? $sort),
|
|
|
|
|
|
'name' => $name,
|
|
|
|
|
|
'content' => $content,
|
|
|
|
|
|
];
|
|
|
|
|
|
$sort++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function normalizeContactPhones(string $raw): string
|
|
|
|
|
|
{
|
|
|
|
|
|
$raw = str_replace(['—', '-', '–'], '-', $raw);
|
|
|
|
|
|
$raw = str_replace([',', ',', ';', ';', '/', '|'], '、', $raw);
|
|
|
|
|
|
$parts = preg_split('/[、\s]+/u', $raw) ?: [];
|
|
|
|
|
|
$parts = array_values(array_filter(array_map('trim', $parts), fn ($p) => $p !== ''));
|
|
|
|
|
|
|
|
|
|
|
|
return implode('、', $parts);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function contactPhoneList(?string $raw): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$normalized = self::normalizeContactPhones((string) $raw);
|
|
|
|
|
|
|
|
|
|
|
|
return $normalized === '' ? [] : explode('、', $normalized);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function applyListFilters(Builder $q, Request $request): void
|
|
|
|
|
|
{
|
|
|
|
|
|
if ($request->filled('keyword')) {
|
|
|
|
|
|
$kw = trim((string) $request->input('keyword'));
|
|
|
|
|
|
if ($kw !== '') {
|
|
|
|
|
|
$q->where(function ($sub) use ($kw) {
|
|
|
|
|
|
$sub->where('name', 'like', '%'.$kw.'%')
|
|
|
|
|
|
->orWhere('org_name', 'like', '%'.$kw.'%')
|
|
|
|
|
|
->orWhere('venue_items', 'like', '%'.$kw.'%');
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ($request->filled('venue_id')) {
|
|
|
|
|
|
$vid = (int) $request->input('venue_id');
|
|
|
|
|
|
if ($vid > 0) {
|
|
|
|
|
|
$q->where(function ($sub) use ($vid) {
|
|
|
|
|
|
$sub->whereJsonContains('venue_ids', $vid)
|
|
|
|
|
|
->orWhere('venue_items', 'like', '%"venue_id":'.$vid.'%');
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ($request->has('is_on_shelf') && $request->input('is_on_shelf') !== '' && $request->input('is_on_shelf') !== null) {
|
|
|
|
|
|
$raw = $request->input('is_on_shelf');
|
|
|
|
|
|
$on = in_array($raw, [1, '1', true, 'true', 'on', 'yes'], true);
|
|
|
|
|
|
$off = in_array($raw, [0, '0', false, 'false', 'off', 'no'], true);
|
|
|
|
|
|
if ($on || $off) {
|
|
|
|
|
|
$q->where('is_on_shelf', $on);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ($request->filled('season')) {
|
|
|
|
|
|
$season = trim((string) $request->input('season'));
|
|
|
|
|
|
if ($season !== '') {
|
|
|
|
|
|
$q->whereJsonContains('seasons', $season);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ($request->filled('grade_level')) {
|
|
|
|
|
|
$grade = trim((string) $request->input('grade_level'));
|
|
|
|
|
|
if ($grade !== '') {
|
|
|
|
|
|
$q->whereJsonContains('grade_levels', $grade);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ($request->filled('org_name')) {
|
|
|
|
|
|
$org = trim((string) $request->input('org_name'));
|
|
|
|
|
|
if ($org !== '') {
|
|
|
|
|
|
$q->where('org_name', 'like', '%'.$org.'%');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @return array{venue_items: array<int, array<string, mixed>>, map_venues: array<int, array<string, mixed>>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function resolveVenueItemsForH5(StudyTour $row, callable $venueTypeColorResolver): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$items = self::venueItemsForRecord($row);
|
|
|
|
|
|
$systemIds = collect($items)
|
|
|
|
|
|
->filter(fn ($item) => ($item['type'] ?? '') === 'system')
|
|
|
|
|
|
->map(fn ($item) => (int) ($item['venue_id'] ?? 0))
|
|
|
|
|
|
->filter(fn ($id) => $id > 0)
|
|
|
|
|
|
->values();
|
|
|
|
|
|
|
|
|
|
|
|
$venueMap = Venue::query()
|
|
|
|
|
|
->whereIn('id', $systemIds->all())
|
|
|
|
|
|
->get(['id', 'name', 'district', 'address', 'cover_image', 'lat', 'lng', 'ticket_type', 'venue_type', 'venue_types'])
|
|
|
|
|
|
->keyBy('id');
|
|
|
|
|
|
|
|
|
|
|
|
$venueItems = [];
|
|
|
|
|
|
$mapVenues = [];
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($items as $item) {
|
|
|
|
|
|
if (($item['type'] ?? '') === 'system') {
|
|
|
|
|
|
$vid = (int) ($item['venue_id'] ?? 0);
|
|
|
|
|
|
$v = $venueMap->get($vid);
|
|
|
|
|
|
if ($v === null) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$arr = $v->toArray();
|
|
|
|
|
|
$arr['type'] = 'system';
|
|
|
|
|
|
$arr['venue_type_color'] = $venueTypeColorResolver($v->venue_type, is_array($v->venue_types) ? $v->venue_types : null);
|
|
|
|
|
|
if (is_array($arr['venue_types'] ?? null)) {
|
|
|
|
|
|
$arr['venue_types'] = array_values($arr['venue_types']);
|
|
|
|
|
|
}
|
|
|
|
|
|
$venueItems[] = $arr;
|
|
|
|
|
|
$lat = (float) ($arr['lat'] ?? 0);
|
|
|
|
|
|
$lng = (float) ($arr['lng'] ?? 0);
|
|
|
|
|
|
if ($lat && $lng) {
|
|
|
|
|
|
$mapVenues[] = $arr;
|
|
|
|
|
|
}
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (($item['type'] ?? '') === 'custom') {
|
|
|
|
|
|
$venueItems[] = [
|
|
|
|
|
|
'type' => 'custom',
|
|
|
|
|
|
'name' => (string) ($item['name'] ?? ''),
|
|
|
|
|
|
'address' => (string) ($item['address'] ?? ''),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'venue_items' => $venueItems,
|
|
|
|
|
|
'map_venues' => $mapVenues,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function listVenueNames(StudyTour $row, $venueMap): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return collect(self::venueItemsForRecord($row))
|
|
|
|
|
|
->map(function ($item) use ($venueMap) {
|
|
|
|
|
|
if (($item['type'] ?? '') === 'system') {
|
|
|
|
|
|
$id = (int) ($item['venue_id'] ?? 0);
|
|
|
|
|
|
$v = is_array($venueMap) ? ($venueMap[$id] ?? null) : $venueMap->get($id);
|
|
|
|
|
|
|
|
|
|
|
|
return $v?->name;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (($item['type'] ?? '') === 'custom') {
|
|
|
|
|
|
return (string) ($item['name'] ?? '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
})
|
|
|
|
|
|
->filter()
|
|
|
|
|
|
->values()
|
|
|
|
|
|
->all();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function listCoverImage(StudyTour $row, $venueMap): ?string
|
|
|
|
|
|
{
|
|
|
|
|
|
$tourCover = trim((string) ($row->cover_image ?? ''));
|
|
|
|
|
|
if ($tourCover !== '') {
|
|
|
|
|
|
return $tourCover;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (self::venueItemsForRecord($row) as $item) {
|
|
|
|
|
|
if (($item['type'] ?? '') !== 'system') {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$id = (int) ($item['venue_id'] ?? 0);
|
|
|
|
|
|
$v = is_array($venueMap) ? ($venueMap[$id] ?? null) : $venueMap->get($id);
|
|
|
|
|
|
$cover = trim((string) ($v?->cover_image ?? ''));
|
|
|
|
|
|
if ($cover !== '') {
|
|
|
|
|
|
return $cover;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function h5ListPayload(StudyTour $row, $venueMap): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$items = self::venueItemsForRecord($row);
|
|
|
|
|
|
$firstSystemItem = collect($items)->first(fn ($item) => ($item['type'] ?? '') === 'system');
|
|
|
|
|
|
$firstSystemId = $firstSystemItem ? (int) ($firstSystemItem['venue_id'] ?? 0) : 0;
|
|
|
|
|
|
$firstVenue = $firstSystemId > 0
|
|
|
|
|
|
? (is_array($venueMap) ? ($venueMap[$firstSystemId] ?? null) : $venueMap->get($firstSystemId))
|
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'id' => $row->id,
|
|
|
|
|
|
'name' => $row->name,
|
|
|
|
|
|
'tags' => array_values($row->tags ?? []),
|
|
|
|
|
|
'org_name' => $row->org_name,
|
|
|
|
|
|
'seasons' => array_values($row->seasons ?? []),
|
|
|
|
|
|
'grade_levels' => array_values($row->grade_levels ?? []),
|
|
|
|
|
|
'venue_names' => self::listVenueNames($row, $venueMap),
|
|
|
|
|
|
'cover_image' => self::listCoverImage($row, $venueMap),
|
|
|
|
|
|
'first_address' => $firstVenue?->address,
|
|
|
|
|
|
'first_district' => $firstVenue?->district,
|
|
|
|
|
|
'venue_count' => count($items),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function h5DetailPayload(StudyTour $row, callable $venueTypeColorResolver): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$venues = self::resolveVenueItemsForH5($row, $venueTypeColorResolver);
|
|
|
|
|
|
$seasonLabels = collect($row->seasons ?? [])
|
|
|
|
|
|
->map(fn ($v) => DictItem::labelFor('study_tour_season', (string) $v) ?? (string) $v)
|
|
|
|
|
|
->filter()
|
|
|
|
|
|
->values()
|
|
|
|
|
|
->all();
|
|
|
|
|
|
$gradeLabels = collect($row->grade_levels ?? [])
|
|
|
|
|
|
->map(fn ($v) => DictItem::labelFor('study_tour_grade_level', (string) $v) ?? (string) $v)
|
|
|
|
|
|
->filter()
|
|
|
|
|
|
->values()
|
|
|
|
|
|
->all();
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'id' => $row->id,
|
|
|
|
|
|
'name' => $row->name,
|
|
|
|
|
|
'tags' => array_values($row->tags ?? []),
|
|
|
|
|
|
'org_name' => $row->org_name,
|
|
|
|
|
|
'seasons' => array_values($row->seasons ?? []),
|
|
|
|
|
|
'season_labels' => $seasonLabels,
|
|
|
|
|
|
'suitable_count' => $row->suitable_count,
|
|
|
|
|
|
'grade_levels' => array_values($row->grade_levels ?? []),
|
|
|
|
|
|
'grade_level_labels' => $gradeLabels,
|
|
|
|
|
|
'duration' => $row->duration,
|
|
|
|
|
|
'contact_person' => $row->contact_person,
|
|
|
|
|
|
'contact_phones' => $row->contact_phones,
|
|
|
|
|
|
'contact_phone_list' => self::contactPhoneList($row->contact_phones),
|
|
|
|
|
|
'image' => $row->cover_image,
|
|
|
|
|
|
'gallery_media' => $row->gallery_media ?? [],
|
|
|
|
|
|
'carousel' => ActivityH5View::buildGalleryCarousel($row),
|
|
|
|
|
|
'intro_html' => $row->intro_html,
|
|
|
|
|
|
'route_plans' => array_values($row->route_plans ?? []),
|
|
|
|
|
|
'courses' => array_values($row->courses ?? []),
|
|
|
|
|
|
'fee_html' => $row->fee_html,
|
|
|
|
|
|
'implementation_html' => $row->implementation_html,
|
|
|
|
|
|
'venue_items' => $venues['venue_items'],
|
|
|
|
|
|
'map_venues' => $venues['map_venues'],
|
|
|
|
|
|
'venues' => $venues['map_venues'],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|