master
lion 3 weeks ago
parent 0518b808b1
commit c135c65b4a

@ -0,0 +1,86 @@
<?php
namespace App\Console\Commands;
use App\Models\Venue;
use App\Support\HtmlToPlainText;
use Illuminate\Console\Command;
/**
* 历史数据中「预约方式」曾用富文本,现改为单行 input 后需去掉 HTML 标签。
* 先执行: php artisan venue:strip-booking-method-html --dry-run
*/
class StripVenueBookingMethodHtmlCommand extends Command
{
protected $signature = 'venue:strip-booking-method-html
{--dry-run : 只列出将更新的场馆 ID不写库}
{--with-snapshot : 同时清理 last_approved_snapshot 内 booking_method}';
protected $description = '批量将 venues.booking_method 从 HTML 转为纯文本(单行)';
public function handle(): int
{
$dry = (bool) $this->option('dry-run');
$withSnapshot = (bool) $this->option('with-snapshot');
$n = 0;
Venue::query()->orderBy('id')->chunkById(100, function ($venues) use ($dry, $withSnapshot, &$n) {
foreach ($venues as $venue) {
if ($this->processVenue($venue, $dry, $withSnapshot)) {
$n++;
}
}
});
if ($dry) {
$this->info("dry-run将更新 {$n} 条记录,未写入数据库。去掉 --dry-run 后执行写入。");
} else {
$this->info("已更新 {$n} 条记录。");
}
return self::SUCCESS;
}
private function processVenue(Venue $venue, bool $dry, bool $withSnapshot): bool
{
$newMethod = HtmlToPlainText::toSingleLine($venue->booking_method);
$methodChanged = (string) ($venue->booking_method ?? '') !== (string) ($newMethod ?? '');
$snapChanged = false;
$newSnapMethod = null;
if ($withSnapshot && is_array($venue->last_approved_snapshot) && array_key_exists('booking_method', $venue->last_approved_snapshot)) {
$raw = $venue->last_approved_snapshot['booking_method'];
if (is_string($raw) || $raw === null) {
$newSnapMethod = HtmlToPlainText::toSingleLine($raw);
$snapChanged = (string) ($raw ?? '') !== (string) ($newSnapMethod ?? '');
}
}
if (! $methodChanged && ! $snapChanged) {
return false;
}
if ($methodChanged) {
$this->line("venue_id={$venue->id} booking_method: 去除 HTML/实体");
}
if ($snapChanged) {
$this->line("venue_id={$venue->id} last_approved_snapshot.booking_method: 去除 HTML/实体");
}
if ($dry) {
return true;
}
if ($methodChanged) {
$venue->booking_method = $newMethod;
}
if ($snapChanged) {
$snap = $venue->last_approved_snapshot;
$snap['booking_method'] = $newSnapMethod;
$venue->last_approved_snapshot = $snap;
}
$venue->save();
return true;
}
}

@ -31,6 +31,12 @@ class ActivityBookingController extends Controller
{
$this->ensureVenuePermission($request, $activity->venue_id);
if (($activity->reservation_type ?? Activity::RESERVATION_TYPE_ONLINE) !== Activity::RESERVATION_TYPE_ONLINE) {
throw ValidationException::withMessages([
'reservation_type' => ['仅「线上预约」的活动可配置场次'],
]);
}
$data = $request->validate([
'booking_audience' => ['required', 'in:individual,group,both'],
'min_people_per_order' => ['nullable', 'integer', 'min:1'],
@ -95,12 +101,12 @@ class ActivityBookingController extends Controller
->where('activity_id', $activity->id)
->where('id', $id)
->first();
if (!$day) {
if (! $day) {
throw ValidationException::withMessages(['days' => ['存在无效的场次 id']]);
}
if ($day->booked_count > $dayQuota) {
throw ValidationException::withMessages([
'days' => ['「' . $name . '」已占用 ' . (int) $day->booked_count . ' 人,总名额不能小于该值'],
'days' => ['「'.$name.'」已占用 '.(int) $day->booked_count.' 人,总名额不能小于该值'],
]);
}
$day->session_name = $name;
@ -178,32 +184,32 @@ class ActivityBookingController extends Controller
$sessionEnd = Carbon::parse($row['session_end_at'])->timezone($tz);
$deadline = Carbon::parse($row['booking_deadline_at'])->timezone($tz);
if (!$sessionStart->isSameDay($sessionEnd)) {
if (! $sessionStart->isSameDay($sessionEnd)) {
throw ValidationException::withMessages([
'days' => ['「' . $name . '」场次的开始与结束须为同一天内'],
'days' => ['「'.$name.'」场次的开始与结束须为同一天内'],
]);
}
if ($sessionEnd->lte($sessionStart)) {
throw ValidationException::withMessages([
'days' => ['「' . $name . '」场次结束时间须晚于开始时间'],
'days' => ['「'.$name.'」场次结束时间须晚于开始时间'],
]);
}
if ($actStartD || $actEndD) {
$dStr = $sessionStart->format('Y-m-d');
if ($actStartD && $dStr < $actStartD) {
throw ValidationException::withMessages([
'days' => ['「' . $name . '」场次开始日期不能早于活动开始日期'],
'days' => ['「'.$name.'」场次开始日期不能早于活动开始日期'],
]);
}
if ($actEndD && $dStr > $actEndD) {
throw ValidationException::withMessages([
'days' => ['「' . $name . '」场次开始日期不能晚于活动结束日期'],
'days' => ['「'.$name.'」场次开始日期不能晚于活动结束日期'],
]);
}
}
if ($deadline->gt($sessionStart)) {
throw ValidationException::withMessages([
'days' => ['「' . $name . '」预约截止时间不能晚于场次开始时间'],
'days' => ['「'.$name.'」预约截止时间不能晚于场次开始时间'],
]);
}
}

@ -7,6 +7,7 @@ use App\Models\Activity;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class ActivityController extends Controller
@ -51,12 +52,17 @@ class ActivityController extends Controller
{
$data = $request->validate([
'venue_id' => ['required', 'integer', 'exists:venues,id'],
'reservation_type' => ['nullable', 'string', Rule::in([Activity::RESERVATION_TYPE_ONLINE, Activity::RESERVATION_TYPE_OFFLINE, Activity::RESERVATION_TYPE_OTHER])],
'location' => ['required', 'string', 'max:500'],
'specific_time' => ['nullable', 'string', 'max:2000'],
'offline_reservation_method' => ['nullable', 'string', 'max:500'],
'external_url' => ['nullable', 'string', 'max:2000'],
'title' => ['required', 'string', 'max:150'],
'summary' => ['nullable', 'string', 'max:255'],
'category' => ['nullable', 'string', 'max:50'],
'quota' => ['nullable', 'integer', 'min:0'],
'start_at' => ['nullable', 'date'],
'end_at' => ['nullable', 'date'],
'start_at' => ['required', 'date'],
'end_at' => ['required', 'date'],
'address' => ['nullable', 'string', 'max:255'],
'contact_phone' => ['nullable', 'string', 'max:255'],
'lat' => ['nullable', 'numeric'],
@ -73,6 +79,8 @@ class ActivityController extends Controller
'sort' => ['nullable', 'integer', 'min:0'],
'is_active' => ['boolean'],
]);
$data = $this->applyReservationTypeDefaults($data);
$this->assertOfflineMethodWhenNeeded($data, null);
$this->ensureVenuePermission($request, (int) $data['venue_id']);
@ -97,6 +105,7 @@ class ActivityController extends Controller
'audit_remark' => null,
'last_approved_snapshot' => null,
]);
return response()->json($activity->load('venue:id,name'), 201);
}
@ -106,12 +115,17 @@ class ActivityController extends Controller
$data = $request->validate([
'venue_id' => ['sometimes', 'integer', 'exists:venues,id'],
'reservation_type' => ['nullable', 'string', Rule::in([Activity::RESERVATION_TYPE_ONLINE, Activity::RESERVATION_TYPE_OFFLINE, Activity::RESERVATION_TYPE_OTHER])],
'location' => ['required', 'string', 'max:500'],
'specific_time' => ['nullable', 'string', 'max:2000'],
'offline_reservation_method' => ['nullable', 'string', 'max:500'],
'external_url' => ['nullable', 'string', 'max:2000'],
'title' => ['sometimes', 'string', 'max:150'],
'summary' => ['sometimes', 'nullable', 'string', 'max:255'],
'category' => ['nullable', 'string', 'max:50'],
'quota' => ['sometimes', 'integer', 'min:0'],
'start_at' => ['nullable', 'date'],
'end_at' => ['nullable', 'date'],
'start_at' => ['required', 'date'],
'end_at' => ['required', 'date'],
'address' => ['nullable', 'string', 'max:255'],
'contact_phone' => ['nullable', 'string', 'max:255'],
'lat' => ['nullable', 'numeric'],
@ -128,6 +142,8 @@ class ActivityController extends Controller
'sort' => ['nullable', 'integer', 'min:0'],
'is_active' => ['boolean'],
]);
$data = $this->applyReservationTypeDefaults($data, $activity);
$this->assertOfflineMethodWhenNeeded($data, $activity);
if (array_key_exists('venue_id', $data)) {
$this->ensureVenuePermission($request, (int) $data['venue_id']);
@ -154,6 +170,7 @@ class ActivityController extends Controller
$activity->tags = array_values($data['tags'] ?? []);
$activity->save();
}
return response()->json($activity->fresh()->load('venue:id,name'));
}
@ -189,8 +206,9 @@ class ActivityController extends Controller
public function toggle(Request $request, Activity $activity): JsonResponse
{
$this->ensureVenuePermission($request, $activity->venue_id);
$activity->is_active = !$activity->is_active;
$activity->is_active = ! $activity->is_active;
$activity->save();
return response()->json($activity->fresh()->load('venue:id,name'));
}
@ -205,6 +223,7 @@ class ActivityController extends Controller
], 422);
}
$activity->delete();
return response()->json(['message' => '删除成功']);
}
@ -218,6 +237,7 @@ class ActivityController extends Controller
$activity->is_active = false;
$activity->save();
}
return response()->json($activity->fresh()->load('venue:id,name'));
}
@ -243,11 +263,53 @@ class ActivityController extends Controller
$data['end_at'] = Carbon::parse($data['end_at'], $tz)->endOfDay();
}
}
if (!empty($data['start_at']) && !empty($data['end_at']) && $data['end_at']->lt($data['start_at'])) {
if (! empty($data['start_at']) && ! empty($data['end_at']) && $data['end_at']->lt($data['start_at'])) {
throw ValidationException::withMessages(['end_at' => ['结束日期不能早于开始日期']]);
}
}
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
private function applyReservationTypeDefaults(array $data, ?Activity $existing = null): array
{
if (array_key_exists('reservation_type', $data)) {
$t = (string) ($data['reservation_type'] ?? '');
$data['reservation_type'] = $t === '' ? Activity::RESERVATION_TYPE_ONLINE : $t;
} elseif ($existing !== null) {
$data['reservation_type'] = (string) ($existing->reservation_type ?? Activity::RESERVATION_TYPE_ONLINE);
} else {
$data['reservation_type'] = Activity::RESERVATION_TYPE_ONLINE;
}
if ($data['reservation_type'] !== Activity::RESERVATION_TYPE_OFFLINE) {
$data['offline_reservation_method'] = null;
} elseif (! array_key_exists('offline_reservation_method', $data) && $existing !== null) {
$data['offline_reservation_method'] = $existing->offline_reservation_method;
}
if ($data['reservation_type'] !== Activity::RESERVATION_TYPE_OTHER) {
$data['external_url'] = null;
} elseif (! array_key_exists('external_url', $data) && $existing !== null) {
$data['external_url'] = $existing->external_url;
}
return $data;
}
/**
* @param array<string, mixed> $data
*/
private function assertOfflineMethodWhenNeeded(array $data, ?Activity $existing = null): void
{
if (($data['reservation_type'] ?? '') !== Activity::RESERVATION_TYPE_OFFLINE) {
return;
}
$m = trim((string) ($data['offline_reservation_method'] ?? $existing?->offline_reservation_method ?? ''));
if ($m === '') {
throw ValidationException::withMessages(['offline_reservation_method' => ['选择「线下预约」时请填写预约方式']]);
}
}
private function restrictByVenue(Request $request, $query): void
{
$user = $request->user();

@ -8,9 +8,10 @@ use App\Models\ActivityDay;
use App\Models\DictItem;
use App\Models\Reservation;
use App\Models\StudyTour;
use App\Models\TicketGrabEvent;
use App\Models\TicketGrabEventVenue;
use App\Models\Venue;
use App\Models\WechatUser;
use App\Models\TicketGrabEvent;
use App\Support\CalendarDateFormat;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
@ -43,7 +44,8 @@ class H5ContentController extends Controller
->paginate($size);
$rows->getCollection()->transform(function ($a) {
$isBookable = $a->activityDays->contains(
$online = $this->activitySupportsOnlineBooking($a);
$isBookable = $online && $a->activityDays->contains(
fn (ActivityDay $d) => $d->isCurrentlyBookable()
);
@ -51,9 +53,9 @@ class H5ContentController extends Controller
'id' => $a->id,
'title' => $a->title,
'summary' => $a->summary,
'image' => $a->cover_image,
'image' => $this->activityListCover($a),
'venue_name' => $a->venue?->name,
'address' => $a->address,
'address' => $a->location ?: $a->address,
'lat' => $a->lat,
'lng' => $a->lng,
'venue_lat' => $a->venue?->lat,
@ -64,6 +66,7 @@ class H5ContentController extends Controller
'registered_count' => (int) ($a->registered_count ?? 0),
'tags' => array_values($a->tags ?? []),
'is_bookable' => $isBookable,
'reservation_type' => $a->reservation_type ?? Activity::RESERVATION_TYPE_ONLINE,
];
});
@ -80,7 +83,8 @@ class H5ContentController extends Controller
->visibleOnH5()
->findOrFail($id);
$isBookable = $a->activityDays->contains(
$onlineBookable = $this->activitySupportsOnlineBooking($a);
$isBookable = $onlineBookable && $a->activityDays->contains(
fn (ActivityDay $d) => $d->isCurrentlyBookable()
);
@ -112,16 +116,19 @@ class H5ContentController extends Controller
return $d->toH5BookingDayArray($already);
});
$cover = $this->activityListCover($a);
return response()->json([
'id' => $a->id,
'title' => $a->title,
'summary' => $a->summary,
'detail_html' => $a->detail_html,
'image' => $a->cover_image,
'image' => $cover,
'gallery_media' => $a->gallery_media ?? [],
'carousel' => $this->buildGalleryCarousel($a),
'carousel' => $this->buildActivityH5Carousel($a),
'venue' => $a->venue,
'address' => $a->address,
'location' => $a->location,
'address' => $a->location ?: $a->address,
'contact_phone' => $a->contact_phone,
'lat' => $a->lat,
'lng' => $a->lng,
@ -132,8 +139,37 @@ class H5ContentController extends Controller
'tags' => array_values($a->tags ?? []),
'reservation_notice' => $a->reservation_notice,
'is_bookable' => $isBookable,
'reservation_type' => $a->reservation_type ?? Activity::RESERVATION_TYPE_ONLINE,
'reservation_type_label' => $this->reservationTypeLabel($a->reservation_type),
'specific_time' => $a->specific_time,
'offline_reservation_method' => $a->offline_reservation_method,
'external_url' => $a->external_url,
'view_count' => (int) ($a->view_count ?? 0),
'external_link_click_count' => (int) ($a->external_link_click_count ?? 0),
'venue_type_color' => $this->resolveVenueTypeColor($a->venue?->venue_type, $a->venue?->venue_types),
'booking_days' => $bookingDays,
'booking_days' => $onlineBookable ? $bookingDays : [],
]);
}
public function recordActivityView(Request $request, int $id): JsonResponse
{
$a = Activity::query()->visibleOnH5()->findOrFail($id);
$a->increment('view_count');
return response()->json([
'ok' => true,
'view_count' => (int) $a->fresh()->view_count,
]);
}
public function recordActivityExternalLinkClick(Request $request, int $id): JsonResponse
{
$a = Activity::query()->visibleOnH5()->findOrFail($id);
$a->increment('external_link_click_count');
return response()->json([
'ok' => true,
'external_link_click_count' => (int) $a->fresh()->external_link_click_count,
]);
}
@ -178,7 +214,7 @@ class H5ContentController extends Controller
return response()->json($rows);
}
public function venueDetail(int $id): JsonResponse
public function venueDetail(Request $request, int $id): JsonResponse
{
$v = Venue::query()->find($id);
if ($v === null) {
@ -200,23 +236,39 @@ class H5ContentController extends Controller
$payload['live_people_count'] = (int) ($display->live_people_count ?? 0);
$payload['venue_type_color'] = $this->resolveVenueTypeColor($display->venue_type, $display->venue_types);
$eid = (int) $request->query('ticket_grab_event_id', 0);
if ($eid > 0) {
$p = TicketGrabEventVenue::query()
->where('ticket_grab_event_id', $eid)
->where('venue_id', $v->id)
->first();
if ($p === null) {
return response()->json(['message' => '该抢票活动未关联此场馆'], 404);
}
$venueOpenTimeForDaily = $payload['open_time'] ?? null;
$payload = $this->mergeTicketGrabVenueIntoH5Payload($payload, $p);
$vo = is_string($venueOpenTimeForDaily) ? trim($venueOpenTimeForDaily) : '';
$payload['venue_open_time'] = $vo !== '' ? $venueOpenTimeForDaily : null;
}
$payload['activities'] = Activity::query()
->where('venue_id', $v->id)
->visibleOnH5()
->orderForH5Listing()
->with('venue:id,cover_image')
->limit(50)
->get(['id', 'title', 'summary', 'cover_image', 'start_at', 'end_at', 'registered_count', 'address'])
->get(['id', 'title', 'summary', 'cover_image', 'start_at', 'end_at', 'registered_count', 'address', 'location', 'venue_id'])
->map(function (Activity $a) {
return [
'id' => $a->id,
'title' => $a->title,
'summary' => $a->summary,
'cover_image' => $a->cover_image,
'cover_image' => $this->activityListCover($a),
'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),
'address' => $a->address,
'address' => $a->location ?: $a->address,
];
})
->values()
@ -225,6 +277,44 @@ class H5ContentController extends Controller
return response()->json($payload);
}
/**
* 抢票进馆页:主卡/详情区仅使用「抢票-参与场馆-编辑详情」的 pivot 字段,无值则为 null不回落场馆主表。
* 轮播 carousel、封面、场馆名称等入参前已按场馆主表算好在此方法外保持不变。
*
* @param array<string, mixed> $payload
* @return array<string, mixed>
*/
private function mergeTicketGrabVenueIntoH5Payload(array $payload, TicketGrabEventVenue $p): array
{
$s = function (?string $t): ?string {
if ($t === null) {
return null;
}
$t = trim($t);
return $t === '' ? null : $t;
};
$payload['open_time'] = $s(is_string($p->opening_hours) ? $p->opening_hours : null);
$payload['address'] = $s(is_string($p->address) ? $p->address : null);
$payload['lat'] = $p->lat === null ? null : (float) $p->lat;
$payload['lng'] = $p->lng === null ? null : (float) $p->lng;
$payload['unit_name'] = $s(is_string($p->unit_name) ? $p->unit_name : null);
$payload['contact_name'] = $s(is_string($p->contact_name) ? $p->contact_name : null);
$payload['contact_phone'] = $s(is_string($p->contact_phone) ? $p->contact_phone : null);
$payload['qr_verify_method'] = $s(is_string($p->qr_verify_method) ? $p->qr_verify_method : null);
$payload['verify_contact_info'] = $s(is_string($p->verify_contact_info) ? $p->verify_contact_info : null);
if (is_string($p->detail_html) && trim($p->detail_html) !== '') {
$payload['detail_html'] = $p->detail_html;
} else {
$payload['detail_html'] = null;
}
$payload['consultation_hours'] = null;
return $payload;
}
/**
* 轮播素材gallery_media可含图片/视频);为空时用封面图兜底。
*
@ -261,6 +351,55 @@ class H5ContentController extends Controller
return $items;
}
private function activitySupportsOnlineBooking(Activity $a): bool
{
$t = (string) ($a->reservation_type ?? Activity::RESERVATION_TYPE_ONLINE);
return $t === '' || $t === Activity::RESERVATION_TYPE_ONLINE;
}
/**
* 列表/详情头图:优先活动封面,无则场馆封面。
*/
private function activityListCover(Activity $a): ?string
{
$c = trim((string) ($a->cover_image ?? ''));
if ($c !== '') {
return $c;
}
$v = trim((string) ($a->venue?->cover_image ?? ''));
return $v !== '' ? $v : null;
}
/**
* H5 轮播:优先活动上传的轮播/封面;皆无则用场馆轮播/封面。
*
* @return array<int, array{type: string, url: string}>
*/
private function buildActivityH5Carousel(Activity $a): array
{
$fromActivity = $this->buildGalleryCarousel($a);
if (count($fromActivity) > 0) {
return $fromActivity;
}
if ($a->venue) {
return $this->buildGalleryCarousel($a->venue);
}
return [];
}
private function reservationTypeLabel(?string $type): string
{
return match ((string) ($type ?? Activity::RESERVATION_TYPE_ONLINE)) {
Activity::RESERVATION_TYPE_OFFLINE => '线下预约',
Activity::RESERVATION_TYPE_OTHER => '其他预约',
default => '线上预约',
};
}
/**
* 与首页地图场馆一致:字典 venue_type 的 item_remark 为色值。
*
@ -387,11 +526,11 @@ class H5ContentController extends Controller
private function authWechatUser(Request $request): ?WechatUser
{
$token = $request->bearerToken();
if (!$token) {
if (! $token) {
return null;
}
$accessToken = PersonalAccessToken::findToken($token);
if (!$accessToken || ! ($accessToken->tokenable instanceof WechatUser)) {
if (! $accessToken || ! ($accessToken->tokenable instanceof WechatUser)) {
return null;
}
@ -431,7 +570,8 @@ class H5ContentController extends Controller
$grabs = $tQuery->get();
$items = $activities->map(function (Activity $a) {
$isBookable = $a->activityDays->contains(
$online = $this->activitySupportsOnlineBooking($a);
$isBookable = $online && $a->activityDays->contains(
fn (ActivityDay $d) => $d->isCurrentlyBookable()
);
@ -440,9 +580,9 @@ class H5ContentController extends Controller
'id' => $a->id,
'title' => $a->title,
'summary' => $a->summary,
'image' => $a->cover_image,
'image' => $this->activityListCover($a),
'venue_name' => $a->venue?->name,
'address' => $a->address,
'address' => $a->location ?: $a->address,
'lat' => $a->lat,
'lng' => $a->lng,
'venue_lat' => $a->venue?->lat,
@ -453,6 +593,7 @@ class H5ContentController extends Controller
'registered_count' => (int) ($a->registered_count ?? 0),
'tags' => array_values($a->tags ?? []),
'is_bookable' => $isBookable,
'reservation_type' => $a->reservation_type ?? Activity::RESERVATION_TYPE_ONLINE,
];
})->merge($grabs->map(function (TicketGrabEvent $e) {
$firstVenue = $e->venues->first();

@ -9,8 +9,8 @@ use App\Models\Blacklist;
use App\Models\PhoneBookingBan;
use App\Models\Reservation;
use App\Models\TicketGrabEvent;
use App\Models\WechatUser;
use App\Models\TicketGrabVenueReleaseDay;
use App\Models\WechatUser;
use App\Services\NoShowBlacklistService;
use App\Services\ReservationExpiryService;
use App\Support\CalendarDateFormat;
@ -36,6 +36,30 @@ class H5ReservationController extends Controller
->visibleOnH5()
->findOrFail($activityId);
if (($activity->reservation_type ?? Activity::RESERVATION_TYPE_ONLINE) !== Activity::RESERVATION_TYPE_ONLINE) {
$mode = self::BOOKING_MODE_BOTH;
return response()->json([
'activity' => [
'id' => $activity->id,
'title' => $activity->title,
'image' => $activity->cover_image,
'booking_audience' => $mode,
'min_people_per_order' => 1,
'max_people_per_order' => 1,
'booking_modes' => [],
'reservation_notice' => $activity->reservation_notice,
'start_at' => optional($activity->start_at)?->toIso8601String(),
'end_at' => optional($activity->end_at)?->toIso8601String(),
'schedule_status' => Activity::computeScheduleStatusFromBounds($activity->start_at, $activity->end_at),
'venue' => $activity->venue,
'reservation_type' => $activity->reservation_type,
],
'days' => [],
'online_booking_disabled' => true,
]);
}
$mode = $activity->booking_audience ?: self::BOOKING_MODE_BOTH;
if ($activity->venue && $activity->venue->appointment_type === 'team_only') {
$mode = self::BOOKING_MODE_GROUP;
@ -103,6 +127,9 @@ class H5ReservationController extends Controller
->with('venue:id,appointment_type')
->visibleOnH5()
->findOrFail($activityId);
if (($activity->reservation_type ?? Activity::RESERVATION_TYPE_ONLINE) !== Activity::RESERVATION_TYPE_ONLINE) {
throw ValidationException::withMessages(['activity' => ['该活动不支持线上预约']]);
}
$mode = $activity->booking_audience ?: self::BOOKING_MODE_BOTH;
if ($activity->venue && $activity->venue->appointment_type === 'team_only') {
$mode = self::BOOKING_MODE_GROUP;
@ -130,7 +157,7 @@ class H5ReservationController extends Controller
->where('id', (int) $data['activity_day_id'])
->where('activity_id', $activity->id)
->first();
if (!$day) {
if (! $day) {
throw ValidationException::withMessages(['activity_day_id' => ['预约场次不存在或不属于该活动']]);
}
if ($day->isSessionMode()) {
@ -344,8 +371,13 @@ class H5ReservationController extends Controller
*/
private function bookingModesFor(string $mode): array
{
if ($mode === self::BOOKING_MODE_INDIVIDUAL) return ['individual'];
if ($mode === self::BOOKING_MODE_GROUP) return ['group'];
if ($mode === self::BOOKING_MODE_INDIVIDUAL) {
return ['individual'];
}
if ($mode === self::BOOKING_MODE_GROUP) {
return ['group'];
}
return ['individual', 'group'];
}
@ -361,8 +393,11 @@ class H5ReservationController extends Controller
// Backward compatible: ticket_mode -> people_count
if ($peopleCount <= 0) {
$ticketMode = (string) ($data['ticket_mode'] ?? '');
if ($ticketMode === 'pair') $peopleCount = 2;
else $peopleCount = 1;
if ($ticketMode === 'pair') {
$peopleCount = 2;
} else {
$peopleCount = 1;
}
}
// Infer booking_type if not provided.
@ -374,6 +409,7 @@ class H5ReservationController extends Controller
if ($bookingType !== 'individual' || $peopleCount !== 1) {
throw ValidationException::withMessages(['people_count' => ['该活动仅支持个人预约(人数=1']]);
}
return [$bookingType, 1];
}
@ -384,6 +420,7 @@ class H5ReservationController extends Controller
if ($peopleCount < $minPeople || $peopleCount > $maxPeople) {
throw ValidationException::withMessages(['people_count' => ["团体人数需在 {$minPeople}-{$maxPeople} 人之间"]]);
}
return [$bookingType, $peopleCount];
}
@ -392,23 +429,25 @@ class H5ReservationController extends Controller
if ($peopleCount !== 1) {
throw ValidationException::withMessages(['people_count' => ['个人预约人数固定为 1 人']]);
}
return [$bookingType, 1];
}
if ($peopleCount < $minPeople || $peopleCount > $maxPeople) {
throw ValidationException::withMessages(['people_count' => ["团体人数需在 {$minPeople}-{$maxPeople} 人之间"]]);
}
return ['group', $peopleCount];
}
private function authWechatUser(Request $request): ?WechatUser
{
$token = $request->bearerToken();
if (!$token) {
if (! $token) {
return null;
}
$accessToken = PersonalAccessToken::findToken($token);
if (!$accessToken || ! ($accessToken->tokenable instanceof WechatUser)) {
if (! $accessToken || ! ($accessToken->tokenable instanceof WechatUser)) {
return null;
}

@ -27,19 +27,31 @@ class H5TicketGrabController extends Controller
{
$e = TicketGrabEvent::query()
->with(['venues' => function ($q) {
$q->select('venues.id', 'name', 'address', 'cover_image', 'district', 'open_time', 'lat', 'lng');
$q->select(
'venues.id',
'venues.name',
'venues.address',
'venues.cover_image',
'venues.district',
'venues.lat',
'venues.lng',
);
}])
->visibleOnH5()
->findOrFail($id);
$e->setAttribute('schedule_status', TicketGrabEvent::computeScheduleStatusFromBounds($e->start_at, $e->end_at));
// 直接使用已关联场馆字段;勿用 toH5Payload() 过滤,否则未过审/未激活的场馆会从列表消失,导致 H5 无「参与场馆」
// 参与场馆列表:开放时间用抢票-场馆「编辑详情」的 opening_hours其余列表字段仍用场馆主表
$venues = $e->venues->map(function (Venue $v) {
$pivot = $v->pivot;
$opening = $pivot?->opening_hours;
$openTime = is_string($opening) && trim($opening) !== '' ? $opening : null;
return [
'id' => (int) $v->id,
'name' => $v->name,
'cover_image' => $v->cover_image,
'district' => $v->district,
'open_time' => $v->open_time,
'open_time' => $openTime,
'address' => $v->address,
'lat' => $v->lat !== null ? (float) $v->lat : null,
'lng' => $v->lng !== null ? (float) $v->lng : null,
@ -373,5 +385,4 @@ class H5TicketGrabController extends Controller
return $access->tokenable;
}
}

@ -3,10 +3,11 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Reservation;
use App\Models\TicketGrabEvent;
use App\Models\TicketGrabEventVenue;
use App\Models\TicketGrabVenueReleaseDay;
use App\Models\Reservation;
use App\Models\Venue;
use App\Services\TicketGrabReleaseDayService;
use App\Support\CalendarDateFormat;
use Carbon\Carbon;
@ -149,6 +150,27 @@ class TicketGrabEventController extends Controller
return response()->json($ticketGrabEvent);
}
/**
* 仅超级管理员;无预约记录时软删除(与活动删除策略一致)。
*/
public function destroy(Request $request, TicketGrabEvent $ticketGrabEvent): JsonResponse
{
if (! $request->user()?->isSuperAdmin()) {
abort(403, '仅超级管理员可删除抢票活动');
}
$this->assertEventAccessible($request, $ticketGrabEvent);
$count = $ticketGrabEvent->reservations()->count();
if ($count > 0) {
return response()->json([
'message' => '该抢票活动已有预约记录,不能删除',
'reservation_count' => $count,
], 422);
}
$ticketGrabEvent->delete();
return response()->json(['message' => '删除成功']);
}
public function releaseConfig(Request $request, TicketGrabEvent $ticketGrabEvent): JsonResponse
{
$this->assertEventAccessible($request, $ticketGrabEvent);
@ -379,6 +401,16 @@ class TicketGrabEventController extends Controller
'venues' => [$isCreate ? 'required' : 'sometimes', 'array', 'min:1'],
'venues.*.venue_id' => ['required', 'integer', 'exists:venues,id'],
'venues.*.venue_total_quota' => ['required', 'integer', 'min:0'],
'venues.*.opening_hours' => ['nullable', 'string', 'max:500'],
'venues.*.address' => ['nullable', 'string', 'max:255'],
'venues.*.lat' => ['nullable', 'numeric'],
'venues.*.lng' => ['nullable', 'numeric'],
'venues.*.unit_name' => ['nullable', 'string', 'max:200'],
'venues.*.contact_name' => ['nullable', 'string', 'max:100'],
'venues.*.contact_phone' => ['nullable', 'string', 'max:100'],
'venues.*.qr_verify_method' => ['nullable', 'string'],
'venues.*.verify_contact_info' => ['nullable', 'string', 'max:500'],
'venues.*.detail_html' => ['nullable', 'string'],
];
if (! $isCreate) {
$rules['venues'] = ['sometimes', 'array', 'min:1'];
@ -403,7 +435,7 @@ class TicketGrabEventController extends Controller
$seen[] = $vid;
TicketGrabEventVenue::query()->updateOrCreate(
['ticket_grab_event_id' => $eventId, 'venue_id' => $vid],
['venue_total_quota' => (int) $v['venue_total_quota']],
$this->pivotAttributesFromVenuePayload($v),
);
}
if ($seen) {
@ -417,4 +449,45 @@ class TicketGrabEventController extends Controller
->sum('venue_total_quota');
TicketGrabEvent::query()->where('id', $eventId)->update(['total_quota' => $sum]);
}
/**
* @param array<string, mixed> $v
* @return array<string, mixed>
*/
private function pivotAttributesFromVenuePayload(array $v): array
{
$str = function (?string $s): ?string {
if ($s === null) {
return null;
}
$t = trim($s);
return $t === '' ? null : $t;
};
$lat = $v['lat'] ?? null;
$lng = $v['lng'] ?? null;
$detailIn = $v['detail_html'] ?? null;
$detail = is_string($detailIn) && trim($detailIn) !== '' ? (string) $detailIn : null;
if ($detail === null) {
$vid = (int) ($v['venue_id'] ?? 0);
$venue = $vid > 0 ? Venue::query()->find($vid) : null;
if ($venue !== null && is_string($venue->detail_html) && trim($venue->detail_html) !== '') {
$detail = $venue->detail_html;
}
}
return [
'venue_total_quota' => (int) $v['venue_total_quota'],
'opening_hours' => $str(isset($v['opening_hours']) ? (string) $v['opening_hours'] : null),
'address' => $str(isset($v['address']) ? (string) $v['address'] : null),
'lat' => $lat === null || $lat === '' ? null : (float) $lat,
'lng' => $lng === null || $lng === '' ? null : (float) $lng,
'unit_name' => $str(isset($v['unit_name']) ? (string) $v['unit_name'] : null),
'contact_name' => $str(isset($v['contact_name']) ? (string) $v['contact_name'] : null),
'contact_phone' => $str(isset($v['contact_phone']) ? (string) $v['contact_phone'] : null),
'qr_verify_method' => $str(isset($v['qr_verify_method']) ? (string) $v['qr_verify_method'] : null),
'verify_contact_info' => $str(isset($v['verify_contact_info']) ? (string) $v['verify_contact_info'] : null),
'detail_html' => $detail,
];
}
}

@ -3,6 +3,8 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\DictItem;
use App\Support\HtmlToPlainText;
use App\Models\StudyTour;
use App\Models\Venue;
use Illuminate\Http\JsonResponse;
@ -34,7 +36,9 @@ class VenueController extends Controller
'booking_method' => ['nullable', 'string'],
'visit_form' => ['nullable', 'string'],
'consultation_hours' => ['nullable', 'string'],
'qr_verify_method' => ['nullable', 'string', 'max:65535'],
'booking_qr_media' => ['nullable', 'array', 'max:20'],
'booking_qr_media.*.type' => ['required_with:booking_qr_media', 'in:image'],
'booking_qr_media.*.url' => ['required_with:booking_qr_media', 'string', 'max:255'],
'address' => ['nullable', 'string', 'max:255'],
'contact_phone' => ['nullable', 'string', 'max:255'],
'lat' => ['nullable', 'numeric'],
@ -66,6 +70,187 @@ class VenueController extends Controller
return response()->json($venue->fresh(), 201);
}
/**
* 超级管理员导出全部场馆为 CSVUTF-8 BOM便于 Excel 打开)。
* 前 22 列与 {@see \App\Services\VenueImportService::buildTemplateSpreadsheet()} 表头、列顺序一致(主题为导入同款英文逗号拼接;预约方式去 HTML 标签为纯文本;场馆详情保留内容便于再导入为富文本)。
*/
public function export(Request $request)
{
$user = $request->user();
abort_unless($user && $user->isSuperAdmin(), 403, '仅超级管理员可导出');
$dictMaps = $this->buildDictLabelMaps();
$rows = Venue::query()->orderBy('sort')->orderByDesc('id')->get();
$filename = '场馆导出-'.now()->format('Ymd-His').'.csv';
return response()->streamDownload(function () use ($rows, $dictMaps) {
$out = fopen('php://output', 'w');
fprintf($out, chr(0xEF).chr(0xBB).chr(0xBF));
// 与导入模板首行一致,另附系统用列(与 VenueImportService::$headers 顺序对齐)
fputcsv($out, array_merge([
'场馆名称', '主题', '行政区', '预约类型', '门票类型', '预约模式', '开放模式', '所属单位', '预约方式', '参观形式', '开放时间', '咨询预约时间', '咨询预约联系电话', '排序', '启用', '纳入人数统计', '场馆地址', '经度', '纬度', '门票说明', '场馆详情', '预约须知',
], [
'编号', '预约二维码', '封面图', '轮播图与视频', '实时在馆人数', '审核状态', '审核备注', '创建时间', '更新时间',
]));
foreach ($rows as $v) {
fputcsv($out, array_merge([
$this->csvCell($v->name),
$this->formatVenueThemeForExport($v, $dictMaps),
$this->dictLabel($dictMaps, 'district', $v->district),
$this->dictLabel($dictMaps, 'venue_appointment_type', $v->appointment_type),
$this->dictLabel($dictMaps, 'ticket_type', $v->ticket_type),
$this->dictLabel($dictMaps, 'venue_booking_mode', $v->booking_mode),
$this->dictLabel($dictMaps, 'venue_open_mode', $v->open_mode),
$this->csvCell($v->unit_name),
$this->plainTextFromHtmlForCsv($v->booking_method),
$this->csvCell($v->visit_form),
$this->csvCell($v->open_time),
$this->csvCell($v->consultation_hours),
$this->csvCell($v->contact_phone),
(int) ($v->sort ?? 0),
$v->is_active ? '是' : '否',
$v->is_included_in_stats ? '是' : '否',
$this->csvCell($v->address),
$v->lng,
$v->lat,
$this->csvCell($v->ticket_content),
$this->csvCell($v->detail_html),
$this->csvCell($v->reservation_notice),
], [
$v->id,
$this->formatMediaUrlsFromArray($v->booking_qr_media, 'url'),
$this->csvCell($v->cover_image),
$this->formatMediaUrlsFromArray($v->gallery_media, 'url', true),
(int) ($v->live_people_count ?? 0),
$this->auditStatusLabelChinese($v->audit_status),
$this->csvCell($v->audit_remark),
$v->created_at?->format('Y-m-d H:i:s') ?? '',
$v->updated_at?->format('Y-m-d H:i:s') ?? '',
]));
}
fclose($out);
}, $filename, ['Content-Type' => 'text/csv; charset=UTF-8']);
}
/**
* @return array<string, array<string, string>> dict_type => item_value => item_label
*/
private function buildDictLabelMaps(): array
{
$types = [
'district',
'venue_type',
'ticket_type',
'venue_appointment_type',
'venue_booking_mode',
'venue_open_mode',
];
$rows = DictItem::query()
->whereIn('dict_type', $types)
->where('is_active', true)
->get(['dict_type', 'item_value', 'item_label']);
$maps = array_fill_keys($types, []);
foreach ($rows as $r) {
$maps[$r->dict_type][(string) $r->item_value] = (string) $r->item_label;
}
return $maps;
}
/**
* @param array<string, array<string, string>> $maps
*/
private function dictLabel(array $maps, string $type, ?string $value): string
{
if ($value === null || $value === '') {
return '';
}
$key = (string) $value;
return $maps[$type][$key] ?? $key;
}
/**
* @param array<string, array<string, string>> $maps
*/
private function formatVenueThemeForExport(Venue $v, array $maps): string
{
$list = $v->venue_types;
if (is_array($list) && count($list) > 0) {
$labels = [];
foreach ($list as $tv) {
$tv = (string) $tv;
$labels[] = $this->dictLabel($maps, 'venue_type', $tv) ?: $tv;
}
$labels = array_values(array_unique($labels));
// 与导入模板「主题」一致多个主题用英文逗号分隔normalizeRowFromCells 按英文逗号拆分)
return $this->csvCell(implode(',', $labels));
}
return $this->csvCell($this->dictLabel($maps, 'venue_type', $v->venue_type));
}
/**
* @param array<int, array<string, mixed>>|null $items
*/
private function formatMediaUrlsFromArray($items, string $key = 'url', bool $includeType = false): string
{
if (! is_array($items) || $items === []) {
return '';
}
$parts = [];
foreach ($items as $it) {
if (! is_array($it)) {
continue;
}
$u = (string) ($it[$key] ?? '');
if ($u === '') {
continue;
}
if ($includeType && isset($it['type'])) {
$parts[] = (string) $it['type'].': '.$u;
} else {
$parts[] = $u;
}
}
return $this->csvCell(implode('', $parts));
}
private function auditStatusLabelChinese(?string $s): string
{
return match ($s) {
Venue::AUDIT_APPROVED => '已通过',
Venue::AUDIT_PENDING => '待审核',
Venue::AUDIT_REJECTED => '已退回',
default => (string) ($s ?? ''),
};
}
private function plainTextFromHtmlForCsv(?string $html): string
{
if ($html === null || $html === '') {
return '';
}
$t = HtmlToPlainText::toSingleLine($html) ?? '';
if (function_exists('mb_strlen') && mb_strlen($t, 'UTF-8') > 10000) {
$t = mb_substr($t, 0, 10000, 'UTF-8').'…';
}
return $this->csvCell($t);
}
private function csvCell(?string $value): string
{
if ($value === null || $value === '') {
return '';
}
return str_replace(["\r\n", "\r", "\n"], ' ', $value);
}
public function index(Request $request): JsonResponse
{
$user = $request->user();
@ -89,15 +274,15 @@ class VenueController extends Controller
if ($keyword !== '') {
$query->where(function ($q) use ($keyword) {
$q->where('name', 'like', '%' . $keyword . '%')
->orWhere('address', 'like', '%' . $keyword . '%')
->orWhere('unit_name', 'like', '%' . $keyword . '%')
->orWhere('open_time', 'like', '%' . $keyword . '%')
->orWhere('reservation_notice', 'like', '%' . $keyword . '%')
->orWhere('ticket_content', 'like', '%' . $keyword . '%')
->orWhere('booking_method', 'like', '%' . $keyword . '%')
->orWhere('visit_form', 'like', '%' . $keyword . '%')
->orWhere('consultation_hours', 'like', '%' . $keyword . '%');
$q->where('name', 'like', '%'.$keyword.'%')
->orWhere('address', 'like', '%'.$keyword.'%')
->orWhere('unit_name', 'like', '%'.$keyword.'%')
->orWhere('open_time', 'like', '%'.$keyword.'%')
->orWhere('reservation_notice', 'like', '%'.$keyword.'%')
->orWhere('ticket_content', 'like', '%'.$keyword.'%')
->orWhere('booking_method', 'like', '%'.$keyword.'%')
->orWhere('visit_form', 'like', '%'.$keyword.'%')
->orWhere('consultation_hours', 'like', '%'.$keyword.'%');
});
}
@ -146,6 +331,7 @@ class VenueController extends Controller
}
$venues = $query->orderBy('venues.sort')->orderByDesc('venues.id')->get();
return response()->json($venues);
}
@ -173,7 +359,9 @@ class VenueController extends Controller
'booking_method' => ['nullable', 'string'],
'visit_form' => ['nullable', 'string'],
'consultation_hours' => ['nullable', 'string'],
'qr_verify_method' => ['nullable', 'string', 'max:65535'],
'booking_qr_media' => ['nullable', 'array', 'max:20'],
'booking_qr_media.*.type' => ['required_with:booking_qr_media', 'in:image'],
'booking_qr_media.*.url' => ['required_with:booking_qr_media', 'string', 'max:255'],
'address' => ['nullable', 'string', 'max:255'],
'contact_phone' => ['nullable', 'string', 'max:255'],
'lat' => ['nullable', 'numeric'],

@ -3,8 +3,8 @@
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
@ -14,8 +14,19 @@ class Activity extends Model
{
use HasFactory, SoftDeletes;
public const RESERVATION_TYPE_ONLINE = 'online';
public const RESERVATION_TYPE_OFFLINE = 'offline';
public const RESERVATION_TYPE_OTHER = 'other';
protected $fillable = [
'venue_id',
'reservation_type',
'location',
'specific_time',
'offline_reservation_method',
'external_url',
'title',
'summary',
'category',
@ -43,6 +54,8 @@ class Activity extends Model
'audit_status',
'audit_remark',
'last_approved_snapshot',
'view_count',
'external_link_click_count',
];
public const AUDIT_APPROVED = 'approved';
@ -64,6 +77,8 @@ class Activity extends Model
'min_people_per_order' => 'integer',
'max_people_per_order' => 'integer',
'last_approved_snapshot' => 'array',
'view_count' => 'integer',
'external_link_click_count' => 'integer',
];
/** 活动待审期间若用快照会与 activity_days 不一致,故前台仅展示已审核通过的记录。 */

@ -78,7 +78,20 @@ class TicketGrabEvent extends Model
public function venues(): BelongsToMany
{
return $this->belongsToMany(Venue::class, 'ticket_grab_event_venue')
->withPivot('venue_total_quota', 'id')
->withPivot(
'venue_total_quota',
'opening_hours',
'address',
'lat',
'lng',
'unit_name',
'contact_name',
'contact_phone',
'qr_verify_method',
'verify_contact_info',
'detail_html',
'id',
)
->withTimestamps();
}

@ -13,10 +13,22 @@ class TicketGrabEventVenue extends Model
'ticket_grab_event_id',
'venue_id',
'venue_total_quota',
'opening_hours',
'address',
'lat',
'lng',
'unit_name',
'contact_name',
'contact_phone',
'qr_verify_method',
'verify_contact_info',
'detail_html',
];
protected $casts = [
'venue_total_quota' => 'integer',
'lat' => 'float',
'lng' => 'float',
];
public function ticketGrabEvent(): BelongsTo

@ -28,7 +28,7 @@ class Venue extends Model
'booking_method',
'visit_form',
'consultation_hours',
'qr_verify_method',
'booking_qr_media',
'address',
'contact_phone',
'lat',
@ -53,14 +53,16 @@ class Venue extends Model
// 预约模式常量
public const BOOKING_MODE_TEAM_ONLY = 'team_only';
public const BOOKING_MODE_ALL_REQUIRED = 'all_required';
public const BOOKING_MODE_TEAM_REQUIRED = 'team_required';
/** @var list<string> */
public const SNAPSHOT_KEYS = [
'name', 'venue_type', 'venue_types', 'unit_name', 'district', 'ticket_type',
'appointment_type', 'booking_mode', 'open_mode', 'open_time', 'reservation_notice',
'ticket_content', 'booking_method', 'visit_form', 'consultation_hours', 'qr_verify_method',
'ticket_content', 'booking_method', 'visit_form', 'consultation_hours', 'booking_qr_media',
'address', 'contact_phone', 'lat', 'lng', 'cover_image', 'gallery_media',
'detail_html', 'live_people_count', 'sort', 'is_active', 'is_included_in_stats',
];
@ -69,6 +71,7 @@ class Venue extends Model
'is_active' => 'boolean',
'is_included_in_stats' => 'boolean',
'gallery_media' => 'array',
'booking_qr_media' => 'array',
'venue_types' => 'array',
'lat' => 'float',
'lng' => 'float',

@ -0,0 +1,31 @@
<?php
namespace App\Support;
/**
* 将富文本/HTML 转为单行纯文本(去标签、解码实体、折叠空白)。
*/
final class HtmlToPlainText
{
public static function toSingleLine(?string $html): ?string
{
if ($html === null || $html === '') {
return $html;
}
if (! str_contains($html, '<') && ! preg_match('/&[a-zA-Z#0-9]+;|&#\d+;|&#x[0-9a-fA-F]+;/', $html)) {
$t = trim((string) preg_replace('/\s+/u', ' ', $html));
$t = str_replace(["\r\n", "\r", "\n"], ' ', $t);
return $t === '' ? null : trim($t);
}
$t = strip_tags($html);
$t = html_entity_decode($t, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$t = (string) preg_replace('/\s+/u', ' ', $t);
$t = str_replace(["\r\n", "\r", "\n"], ' ', $t);
$t = trim($t);
return $t === '' ? null : $t;
}
}

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('ticket_grab_event_venue', function (Blueprint $table) {
$table->string('opening_hours', 500)->nullable()->after('venue_total_quota')->comment('开放时间(文案)');
$table->string('address', 255)->nullable();
$table->decimal('lat', 10, 7)->nullable();
$table->decimal('lng', 10, 7)->nullable();
$table->string('unit_name', 200)->nullable()->comment('所在单位');
$table->string('contact_name', 100)->nullable()->comment('联系人');
$table->string('contact_phone', 100)->nullable()->comment('联系方式');
$table->text('qr_verify_method')->nullable()->comment('二维码核销方式');
$table->string('verify_contact_info', 500)->nullable()->comment('核销及检票联系方式');
$table->string('daily_open_time', 500)->nullable()->comment('日常开放时间');
$table->longText('detail_html')->nullable()->comment('详情(富文本)');
});
}
public function down(): void
{
Schema::table('ticket_grab_event_venue', function (Blueprint $table) {
$table->dropColumn([
'opening_hours', 'address', 'lat', 'lng', 'unit_name', 'contact_name',
'contact_phone', 'qr_verify_method', 'verify_contact_info', 'daily_open_time', 'detail_html',
]);
});
}
};

@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (Schema::hasColumn('ticket_grab_event_venue', 'daily_open_time')) {
Schema::table('ticket_grab_event_venue', function (Blueprint $table) {
$table->dropColumn('daily_open_time');
});
}
}
public function down(): void
{
Schema::table('ticket_grab_event_venue', function (Blueprint $table) {
$table->string('daily_open_time', 500)->nullable()->after('verify_contact_info')->comment('日常开放时间');
});
}
};

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (Schema::hasTable('venues')) {
if (! Schema::hasColumn('venues', 'booking_qr_media')) {
Schema::table('venues', function (Blueprint $table) {
$table->json('booking_qr_media')->nullable()->comment('预约二维码多图,结构同 gallery_media 仅 image');
});
}
if (Schema::hasColumn('venues', 'qr_verify_method')) {
Schema::table('venues', function (Blueprint $table) {
$table->dropColumn('qr_verify_method');
});
}
}
}
public function down(): void
{
if (Schema::hasTable('venues')) {
if (Schema::hasColumn('venues', 'booking_qr_media')) {
Schema::table('venues', function (Blueprint $table) {
$table->dropColumn('booking_qr_media');
});
}
if (! Schema::hasColumn('venues', 'qr_verify_method')) {
Schema::table('venues', function (Blueprint $table) {
$table->text('qr_verify_method')->nullable()->comment('H5 抢票馆详情:二维码/核销入馆方式说明');
});
}
}
}
};

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('activities', function (Blueprint $table) {
$table->string('reservation_type', 32)->default('online')->after('venue_id');
$table->string('location', 500)->nullable()->after('end_at');
$table->string('specific_time', 2000)->nullable()->after('location');
$table->text('offline_reservation_method')->nullable()->after('specific_time');
$table->string('external_url', 2000)->nullable()->after('offline_reservation_method');
$table->unsignedInteger('view_count')->default(0);
$table->unsignedInteger('external_link_click_count')->default(0);
});
}
public function down(): void
{
Schema::table('activities', function (Blueprint $table) {
$table->dropColumn([
'reservation_type',
'location',
'specific_time',
'offline_reservation_method',
'external_url',
'view_count',
'external_link_click_count',
]);
});
}
};

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
.activity-address-coord-row[data-v-7b328d75]{flex-wrap:wrap;align-items:center;gap:12px;width:100%;display:flex}.activity-address-coord-row__address[data-v-7b328d75]{flex:45%;min-width:320px;max-width:100%}.activity-address-coord-row__lng[data-v-7b328d75],.activity-address-coord-row__lat[data-v-7b328d75]{flex:180px;width:200px;min-width:180px}.activity-address-coord-row__map[data-v-7b328d75]{flex-shrink:0}.activity-cover-carousel-wrap[data-v-7b328d75]{flex-wrap:wrap;align-items:flex-start;gap:20px;width:100%;display:flex}.activity-cover-carousel-row__col[data-v-7b328d75]{flex:320px;min-width:min(100%,320px)}.activity-cover-carousel-row__sub[data-v-7b328d75]{color:var(--color-text-1);margin-bottom:8px;font-weight:500}.activity-cover-thumb[data-v-7b328d75]{object-fit:cover;cursor:zoom-in;border:1px solid #e5e6eb;border-radius:4px;width:120px;height:70px}.activity-gallery-grid[data-v-7b328d75]{flex-wrap:wrap;align-items:flex-start;gap:12px;width:100%;display:flex}.activity-gallery-item[data-v-7b328d75]{flex-direction:column;align-items:flex-start;gap:8px;display:flex}.activity-gallery-thumb[data-v-7b328d75]{object-fit:cover;cursor:zoom-in;border:1px solid #e5e6eb;border-radius:4px;width:120px;height:70px}.activity-gallery-thumb--video[data-v-7b328d75]{display:block}

@ -1 +0,0 @@
.activity-address-coord-row[data-v-e9e0d75c]{flex-wrap:wrap;align-items:center;gap:12px;width:100%;display:flex}.activity-address-coord-row__address[data-v-e9e0d75c]{flex:45%;min-width:320px;max-width:100%}.activity-address-coord-row__lng[data-v-e9e0d75c],.activity-address-coord-row__lat[data-v-e9e0d75c]{flex:180px;width:200px;min-width:180px}.activity-address-coord-row__map[data-v-e9e0d75c]{flex-shrink:0}.activity-cover-carousel-wrap[data-v-e9e0d75c]{flex-wrap:wrap;align-items:flex-start;gap:20px;width:100%;display:flex}.activity-cover-carousel-row__col[data-v-e9e0d75c]{flex:320px;min-width:min(100%,320px)}.activity-cover-carousel-row__sub[data-v-e9e0d75c]{color:var(--color-text-1);margin-bottom:8px;font-weight:500}.activity-cover-thumb[data-v-e9e0d75c]{object-fit:cover;cursor:zoom-in;border:1px solid #e5e6eb;border-radius:4px;width:120px;height:70px}.activity-gallery-grid[data-v-e9e0d75c]{flex-wrap:wrap;align-items:flex-start;gap:12px;width:100%;display:flex}.activity-gallery-item[data-v-e9e0d75c]{flex-direction:column;align-items:flex-start;gap:8px;display:flex}.activity-gallery-thumb[data-v-e9e0d75c]{object-fit:cover;cursor:zoom-in;border:1px solid #e5e6eb;border-radius:4px;width:120px;height:70px}.activity-gallery-thumb--video[data-v-e9e0d75c]{display:block}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`Alerts`,setup(n){return(n,i)=>(e(),t(r,{title:`客流监控 / 异常告警`}))}});export{i as default};

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`Alerts`,setup(n){return(n,i)=>(e(),t(r,{title:`客流监控 / 异常告警`}))}});export{i as default};

@ -0,0 +1 @@
import{n as e}from"./axios-CiYFffbI.js";import{I as t,N as n,V as r,Y as i,_ as a,d as o,it as s,kt as c,nt as l,ut as u,v as d,y as f}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{n as p}from"./index-AJjRNeYZ.js";import{t as m}from"./datetime-3T8f3S0H.js";import{t as h}from"./listTable-DXuZ0yk8.js";var g=f({__name:`AuditLogs`,setup(f){let g=s(!1),_=s([]),v=l({current:1,pageSize:20,total:0}),y=l({keyword:``,method:`all`,status_code:void 0,dateRange:[]});async function b(){g.value=!0;try{let{data:e}=await p.get(`/audit-logs`,{params:{keyword:y.keyword||void 0,method:y.method,status_code:y.status_code||void 0,start_date:y.dateRange?.[0]||void 0,end_date:y.dateRange?.[1]||void 0,page:v.current,page_size:v.pageSize}});_.value=e.data,v.total=e.total}catch(t){e.error(t?.response?.data?.message??`加载操作日志失败`)}finally{g.value=!1}}function x(){v.current=1,b()}function S(e){v.current=e,b()}function C(e){return e===`super_admin`?`超级管理员`:e===`venue_admin`?`场馆管理员`:`-`}return n(b),(e,n)=>{let s=r(`a-input`),l=r(`a-option`),f=r(`a-select`),p=r(`a-input-number`),w=r(`a-range-picker`),T=r(`a-button`),E=r(`a-space`),D=r(`a-table-column`),O=r(`a-typography-paragraph`),k=r(`a-table`),A=r(`a-card`);return t(),o(A,{title:`用户与权限 / 操作日志`},{default:i(()=>[d(E,{wrap:``,size:12,style:{"margin-bottom":`12px`}},{default:i(()=>[d(s,{modelValue:y.keyword,"onUpdate:modelValue":n[0]||=e=>y.keyword=e,placeholder:`操作人/路径/动作`,"allow-clear":``,style:{width:`240px`}},null,8,[`modelValue`]),d(f,{modelValue:y.method,"onUpdate:modelValue":n[1]||=e=>y.method=e,style:{width:`120px`}},{default:i(()=>[d(l,{value:`all`},{default:i(()=>[...n[4]||=[a(`全部方法`,-1)]]),_:1}),d(l,{value:`POST`},{default:i(()=>[...n[5]||=[a(`POST`,-1)]]),_:1}),d(l,{value:`PUT`},{default:i(()=>[...n[6]||=[a(`PUT`,-1)]]),_:1}),d(l,{value:`PATCH`},{default:i(()=>[...n[7]||=[a(`PATCH`,-1)]]),_:1}),d(l,{value:`DELETE`},{default:i(()=>[...n[8]||=[a(`DELETE`,-1)]]),_:1})]),_:1},8,[`modelValue`]),d(p,{modelValue:y.status_code,"onUpdate:modelValue":n[2]||=e=>y.status_code=e,min:100,max:599,placeholder:`状态码`,style:{width:`120px`}},null,8,[`modelValue`]),d(w,{modelValue:y.dateRange,"onUpdate:modelValue":n[3]||=e=>y.dateRange=e,style:{width:`260px`}},null,8,[`modelValue`]),d(T,{type:`primary`,onClick:x},{default:i(()=>[...n[9]||=[a(`查询`,-1)]]),_:1}),d(T,{onClick:b},{default:i(()=>[...n[10]||=[a(`刷新`,-1)]]),_:1})]),_:1}),d(k,{class:`list-data-table`,scroll:{x:u(h)},data:_.value,loading:g.value,"row-key":`id`,pagination:{current:v.current,pageSize:v.pageSize,total:v.total,showTotal:!0},onPageChange:S},{columns:i(()=>[d(D,{title:`ID`,"data-index":`id`,width:88}),d(D,{title:`操作人`,"data-index":`username`,width:140,ellipsis:!0,tooltip:!0}),d(D,{title:`角色`,width:120},{cell:i(({record:e})=>[a(c(C(e.role)),1)]),_:1}),d(D,{title:`方法`,"data-index":`method`,width:90}),d(D,{title:`路径`,"data-index":`path`,width:260,ellipsis:!0,tooltip:!0}),d(D,{title:`动作`,"data-index":`action`,width:220,ellipsis:!0,tooltip:!0}),d(D,{title:`状态码`,"data-index":`status_code`,width:100}),d(D,{title:`IP`,"data-index":`ip`,width:140,ellipsis:!0,tooltip:!0}),d(D,{title:`时间`,width:190},{cell:i(({record:e})=>[a(c(u(m)(e.created_at)),1)]),_:1}),d(D,{title:`请求参数`,"min-width":260},{cell:i(({record:e})=>[d(O,{ellipsis:{rows:2}},{default:i(()=>[a(c(e.request_payload?JSON.stringify(e.request_payload):`-`),1)]),_:2},1024)]),_:1})]),_:1},8,[`scroll`,`data`,`loading`,`pagination`])]),_:1})}}});export{g as default};

@ -1 +0,0 @@
import{n as e}from"./axios-Cze8nXLL.js";import{At as t,I as n,N as r,V as i,X as a,_ as o,at as s,d as c,dt as l,rt as u,v as d,y as f}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{n as p}from"./index-qyPO_3-6.js";import{t as m}from"./datetime-3T8f3S0H.js";import{t as h}from"./listTable-DXuZ0yk8.js";var g=f({__name:`AuditLogs`,setup(f){let g=s(!1),_=s([]),v=u({current:1,pageSize:20,total:0}),y=u({keyword:``,method:`all`,status_code:void 0,dateRange:[]});async function b(){g.value=!0;try{let{data:e}=await p.get(`/audit-logs`,{params:{keyword:y.keyword||void 0,method:y.method,status_code:y.status_code||void 0,start_date:y.dateRange?.[0]||void 0,end_date:y.dateRange?.[1]||void 0,page:v.current,page_size:v.pageSize}});_.value=e.data,v.total=e.total}catch(t){e.error(t?.response?.data?.message??`加载操作日志失败`)}finally{g.value=!1}}function x(){v.current=1,b()}function S(e){v.current=e,b()}function C(e){return e===`super_admin`?`超级管理员`:e===`venue_admin`?`场馆管理员`:`-`}return r(b),(e,r)=>{let s=i(`a-input`),u=i(`a-option`),f=i(`a-select`),p=i(`a-input-number`),w=i(`a-range-picker`),T=i(`a-button`),E=i(`a-space`),D=i(`a-table-column`),O=i(`a-typography-paragraph`),k=i(`a-table`),A=i(`a-card`);return n(),c(A,{title:`用户与权限 / 操作日志`},{default:a(()=>[d(E,{wrap:``,size:12,style:{"margin-bottom":`12px`}},{default:a(()=>[d(s,{modelValue:y.keyword,"onUpdate:modelValue":r[0]||=e=>y.keyword=e,placeholder:`操作人/路径/动作`,"allow-clear":``,style:{width:`240px`}},null,8,[`modelValue`]),d(f,{modelValue:y.method,"onUpdate:modelValue":r[1]||=e=>y.method=e,style:{width:`120px`}},{default:a(()=>[d(u,{value:`all`},{default:a(()=>[...r[4]||=[o(`全部方法`,-1)]]),_:1}),d(u,{value:`POST`},{default:a(()=>[...r[5]||=[o(`POST`,-1)]]),_:1}),d(u,{value:`PUT`},{default:a(()=>[...r[6]||=[o(`PUT`,-1)]]),_:1}),d(u,{value:`PATCH`},{default:a(()=>[...r[7]||=[o(`PATCH`,-1)]]),_:1}),d(u,{value:`DELETE`},{default:a(()=>[...r[8]||=[o(`DELETE`,-1)]]),_:1})]),_:1},8,[`modelValue`]),d(p,{modelValue:y.status_code,"onUpdate:modelValue":r[2]||=e=>y.status_code=e,min:100,max:599,placeholder:`状态码`,style:{width:`120px`}},null,8,[`modelValue`]),d(w,{modelValue:y.dateRange,"onUpdate:modelValue":r[3]||=e=>y.dateRange=e,style:{width:`260px`}},null,8,[`modelValue`]),d(T,{type:`primary`,onClick:x},{default:a(()=>[...r[9]||=[o(`查询`,-1)]]),_:1}),d(T,{onClick:b},{default:a(()=>[...r[10]||=[o(`刷新`,-1)]]),_:1})]),_:1}),d(k,{class:`list-data-table`,scroll:{x:l(h)},data:_.value,loading:g.value,"row-key":`id`,pagination:{current:v.current,pageSize:v.pageSize,total:v.total,showTotal:!0},onPageChange:S},{columns:a(()=>[d(D,{title:`ID`,"data-index":`id`,width:88}),d(D,{title:`操作人`,"data-index":`username`,width:140,ellipsis:!0,tooltip:!0}),d(D,{title:`角色`,width:120},{cell:a(({record:e})=>[o(t(C(e.role)),1)]),_:1}),d(D,{title:`方法`,"data-index":`method`,width:90}),d(D,{title:`路径`,"data-index":`path`,width:260,ellipsis:!0,tooltip:!0}),d(D,{title:`动作`,"data-index":`action`,width:220,ellipsis:!0,tooltip:!0}),d(D,{title:`状态码`,"data-index":`status_code`,width:100}),d(D,{title:`IP`,"data-index":`ip`,width:140,ellipsis:!0,tooltip:!0}),d(D,{title:`时间`,width:190},{cell:a(({record:e})=>[o(t(l(m)(e.created_at)),1)]),_:1}),d(D,{title:`请求参数`,"min-width":260},{cell:a(({record:e})=>[d(O,{ellipsis:{rows:2}},{default:a(()=>[o(t(e.request_payload?JSON.stringify(e.request_payload):`-`),1)]),_:2},1024)]),_:1})]),_:1},8,[`scroll`,`data`,`loading`,`pagination`])]),_:1})}}});export{g as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`Categories`,setup(n){return(n,i)=>(e(),t(r,{title:`数据统计 / 类别分析`}))}});export{i as default};

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`Categories`,setup(n){return(n,i)=>(e(),t(r,{title:`数据统计 / 类别分析`}))}});export{i as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`Exports`,setup(n){return(n,i)=>(e(),t(r,{title:`数据统计 / 报表导出`,desc:`后续接入按时间/区域/类别筛选与 Excel 导出下载。`}))}});export{i as default};

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`Exports`,setup(n){return(n,i)=>(e(),t(r,{title:`数据统计 / 报表导出`,desc:`后续接入按时间/区域/类别筛选与 Excel 导出下载。`}))}});export{i as default};

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`Leaderboard`,setup(n){return(n,i)=>(e(),t(r,{title:`客流监控 / 活跃指数排行榜`}))}});export{i as default};

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`Leaderboard`,setup(n){return(n,i)=>(e(),t(r,{title:`客流监控 / 活跃指数排行榜`}))}});export{i as default};

@ -1 +1 @@
import{n as e}from"./axios-Cze8nXLL.js";import{I as t,V as n,X as r,_ as i,at as a,p as o,rt as s,v as c,y as l}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{i as u,n as d,t as f}from"./index-qyPO_3-6.js";var p={style:{height:`100vh`,display:`grid`,"place-items":`center`,background:`var(--color-fill-2)`}},m=l({__name:`Login`,setup(l){let m=u(),h=a(!1),g=s({username:`admin`,password:`admin123456`});async function _(){h.value=!0;try{let{data:t}=await d.post(`/auth/login`,g);localStorage.setItem(f,t.token),e.success(`登录成功`),m.replace(`/dashboard`)}catch(t){e.error(t?.response?.data?.message??`登录失败`)}finally{h.value=!1}}return(e,a)=>{let s=n(`a-input`),l=n(`a-form-item`),u=n(`a-input-password`),d=n(`a-button`),f=n(`a-form`),m=n(`a-card`);return t(),o(`div`,p,[c(m,{title:`苏州市科普场馆地图管理后台登录`,style:{width:`380px`}},{default:r(()=>[c(f,{model:g,layout:`vertical`,onSubmitSuccess:_},{default:r(()=>[c(l,{field:`username`,label:`用户名`},{default:r(()=>[c(s,{modelValue:g.username,"onUpdate:modelValue":a[0]||=e=>g.username=e,placeholder:`请输入用户名`},null,8,[`modelValue`])]),_:1}),c(l,{field:`password`,label:`密码`},{default:r(()=>[c(u,{modelValue:g.password,"onUpdate:modelValue":a[1]||=e=>g.password=e,placeholder:`请输入密码`},null,8,[`modelValue`])]),_:1}),c(d,{type:`primary`,long:``,loading:h.value,onClick:_},{default:r(()=>[...a[2]||=[i(`登录`,-1)]]),_:1},8,[`loading`])]),_:1},8,[`model`])]),_:1})])}}});export{m as default};
import{n as e}from"./axios-CiYFffbI.js";import{I as t,V as n,Y as r,_ as i,it as a,nt as o,p as s,v as c,y as l}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{i as u,n as d,t as f}from"./index-AJjRNeYZ.js";var p={style:{height:`100vh`,display:`grid`,"place-items":`center`,background:`var(--color-fill-2)`}},m=l({__name:`Login`,setup(l){let m=u(),h=a(!1),g=o({username:`admin`,password:`admin123456`});async function _(){h.value=!0;try{let{data:t}=await d.post(`/auth/login`,g);localStorage.setItem(f,t.token),e.success(`登录成功`),m.replace(`/dashboard`)}catch(t){e.error(t?.response?.data?.message??`登录失败`)}finally{h.value=!1}}return(e,a)=>{let o=n(`a-input`),l=n(`a-form-item`),u=n(`a-input-password`),d=n(`a-button`),f=n(`a-form`),m=n(`a-card`);return t(),s(`div`,p,[c(m,{title:`苏州市科普场馆地图管理后台登录`,style:{width:`380px`}},{default:r(()=>[c(f,{model:g,layout:`vertical`,onSubmitSuccess:_},{default:r(()=>[c(l,{field:`username`,label:`用户名`},{default:r(()=>[c(o,{modelValue:g.username,"onUpdate:modelValue":a[0]||=e=>g.username=e,placeholder:`请输入用户名`},null,8,[`modelValue`])]),_:1}),c(l,{field:`password`,label:`密码`},{default:r(()=>[c(u,{modelValue:g.password,"onUpdate:modelValue":a[1]||=e=>g.password=e,placeholder:`请输入密码`},null,8,[`modelValue`])]),_:1}),c(d,{type:`primary`,long:``,loading:h.value,onClick:_},{default:r(()=>[...a[2]||=[i(`登录`,-1)]]),_:1},8,[`loading`])]),_:1},8,[`model`])]),_:1})])}}});export{m as default};

@ -1 +1 @@
import{I as e,V as t,X as n,_ as r,d as i,v as a}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{o}from"./index-qyPO_3-6.js";var s={};function c(o,s){let c=t(`a-alert`),l=t(`a-descriptions-item`),u=t(`a-descriptions`),d=t(`a-card`);return e(),i(d,{title:`系统设置 / 地图与第三方配置`},{default:n(()=>[a(c,{type:`info`,style:{"margin-bottom":`12px`}},{default:n(()=>[...s[0]||=[r(` 当前后台场馆地图选点已使用腾讯地图,坐标统一为 GCJ-02火星坐标系`,-1)]]),_:1}),a(u,{column:1,bordered:``},{default:n(()=>[a(l,{label:`前端地图Key`},{default:n(()=>[...s[1]||=[r(" 在 `code/szkp-map-web/.env` 配置 `VITE_TENCENT_MAP_KEY=你的腾讯地图JS_KEY` ",-1)]]),_:1}),a(l,{label:`地图外链 referer`},{default:n(()=>[...s[2]||=[r(" 在 `code/szkp-map-web/.env` 配置 `VITE_TENCENT_MAP_REFERER=你的应用标识` ",-1)]]),_:1}),a(l,{label:`后端服务Key`},{default:n(()=>[...s[3]||=[r(" 在 `code/szkp-map-service/.env` 配置 `TENCENT_MAP_SERVER_KEY=你的腾讯地图WebService_KEY` ",-1)]]),_:1})]),_:1})]),_:1})}var l=o(s,[[`render`,c]]);export{l as default};
import{I as e,V as t,Y as n,_ as r,d as i,v as a}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{o}from"./index-AJjRNeYZ.js";var s={};function c(o,s){let c=t(`a-alert`),l=t(`a-descriptions-item`),u=t(`a-descriptions`),d=t(`a-card`);return e(),i(d,{title:`系统设置 / 地图与第三方配置`},{default:n(()=>[a(c,{type:`info`,style:{"margin-bottom":`12px`}},{default:n(()=>[...s[0]||=[r(` 当前后台场馆地图选点已使用腾讯地图,坐标统一为 GCJ-02火星坐标系`,-1)]]),_:1}),a(u,{column:1,bordered:``},{default:n(()=>[a(l,{label:`前端地图Key`},{default:n(()=>[...s[1]||=[r(" 在 `code/szkp-map-web/.env` 配置 `VITE_TENCENT_MAP_KEY=你的腾讯地图JS_KEY` ",-1)]]),_:1}),a(l,{label:`地图外链 referer`},{default:n(()=>[...s[2]||=[r(" 在 `code/szkp-map-web/.env` 配置 `VITE_TENCENT_MAP_REFERER=你的应用标识` ",-1)]]),_:1}),a(l,{label:`后端服务Key`},{default:n(()=>[...s[3]||=[r(" 在 `code/szkp-map-service/.env` 配置 `TENCENT_MAP_SERVER_KEY=你的腾讯地图WebService_KEY` ",-1)]]),_:1})]),_:1})]),_:1})}var l=o(s,[[`render`,c]]);export{l as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`Monitor`,setup(n){return(n,i)=>(e(),t(r,{title:`客流监控 / 实时客流监控`,desc:`后续接入 50 个重点场馆实时客流数据源。`}))}});export{i as default};

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`Monitor`,setup(n){return(n,i)=>(e(),t(r,{title:`客流监控 / 实时客流监控`,desc:`后续接入 50 个重点场馆实时客流数据源。`}))}});export{i as default};

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`Notifications`,setup(n){return(n,i)=>(e(),t(r,{title:`系统设置 / 消息通知`}))}});export{i as default};

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`Notifications`,setup(n){return(n,i)=>(e(),t(r,{title:`系统设置 / 消息通知`}))}});export{i as default};

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`Overview`,setup(n){return(n,i)=>(e(),t(r,{title:`数据统计 / 综合统计`}))}});export{i as default};

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`Overview`,setup(n){return(n,i)=>(e(),t(r,{title:`数据统计 / 综合统计`}))}});export{i as default};

@ -0,0 +1 @@
import{I as e,V as t,Y as n,_ as r,d as i,kt as a,y as o}from"./runtime-core.esm-bundler-CnFWH3R5.js";var s=o({__name:`PagePlaceholder`,props:{title:{},desc:{}},setup(o){return(s,c)=>{let l=t(`a-typography-paragraph`),u=t(`a-empty`),d=t(`a-card`);return e(),i(d,{title:o.title,bordered:!1},{default:n(()=>[o.desc?(e(),i(l,{key:0,style:{"margin-bottom":`0`}},{default:n(()=>[r(a(o.desc),1)]),_:1})):(e(),i(u,{key:1,description:`页面待实现`}))]),_:1},8,[`title`])}}});export{s as t};

@ -1 +0,0 @@
import{At as e,I as t,V as n,X as r,_ as i,d as a,y as o}from"./runtime-core.esm-bundler-CP0MNZrl.js";var s=o({__name:`PagePlaceholder`,props:{title:{},desc:{}},setup(o){return(s,c)=>{let l=n(`a-typography-paragraph`),u=n(`a-empty`),d=n(`a-card`);return t(),a(d,{title:o.title,bordered:!1},{default:r(()=>[o.desc?(t(),a(l,{key:0,style:{"margin-bottom":`0`}},{default:r(()=>[i(e(o.desc),1)]),_:1})):(t(),a(u,{key:1,description:`页面待实现`}))]),_:1},8,[`title`])}}});export{s as t};

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`Regions`,setup(n){return(n,i)=>(e(),t(r,{title:`数据统计 / 区域分析`}))}});export{i as default};

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`Regions`,setup(n){return(n,i)=>(e(),t(r,{title:`数据统计 / 区域分析`}))}});export{i as default};

@ -1 +1 @@
import{n as e}from"./axios-Cze8nXLL.js";import{At as t,I as n,N as r,V as i,X as a,_ as o,at as s,d as c,f as l,i as u,l as d,p as f,v as p,y as m,z as h}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{n as g}from"./index-qyPO_3-6.js";var _=m({__name:`Roles`,setup(m){let _=s(!1),v=s(!1),y=s(!1),b=s([]),x=s([]),S=s(`super_admin`),C=s([]),w=d(()=>{let e=new Map;b.value.forEach(t=>e.set(t.id,{key:t.id,title:t.name,children:[]}));let t=[];return b.value.forEach(n=>{let r=e.get(n.id);n.parent_id>0&&e.has(n.parent_id)?e.get(n.parent_id).children.push(r):t.push(r)}),t}),T=d(()=>x.value.find(e=>e.role===S.value));async function E(){let{data:e}=await g.get(`/me`);y.value=e?.role===`super_admin`}async function D(){_.value=!0;try{let{data:e}=await g.get(`/role-menu-permissions`);b.value=e.menus||[],x.value=e.roles||[];let t=x.value[0];t&&(S.value=t.role,C.value=[...t.menu_ids||[]])}catch(t){e.error(t?.response?.data?.message??`加载角色菜单权限失败`)}finally{_.value=!1}}function O(e){S.value=e,C.value=[...x.value.find(t=>t.role===e)?.menu_ids||[]]}function k(e){C.value=e.map(e=>Number(e))}async function A(){if(y.value){v.value=!0;try{await g.put(`/role-menu-permissions/${S.value}`,{menu_ids:C.value});let t=x.value.find(e=>e.role===S.value);t&&(t.menu_ids=[...C.value]),e.success(`角色菜单权限保存成功`)}catch(t){e.error(t?.response?.data?.message??`保存失败`)}finally{v.value=!1}}}return r(async()=>{await E(),await D()}),(e,r)=>{let s=i(`a-alert`),d=i(`a-button`),m=i(`a-space`),g=i(`a-card`),b=i(`a-tree`),E=i(`a-spin`);return n(),c(g,{title:`用户与权限 / 角色管理(菜单权限)`},{default:a(()=>[p(s,{style:{"margin-bottom":`12px`}},{default:a(()=>[...r[0]||=[o(` 当前仅控制“每个角色可查看哪些菜单”;接口级细粒度权限后续可继续扩展。 `,-1)]]),_:1}),y.value?l(``,!0):(n(),c(s,{key:0,type:`info`,style:{"margin-bottom":`12px`}},{default:a(()=>[...r[1]||=[o(` 当前为只读模式,仅超级管理员可以修改角色菜单权限。 `,-1)]]),_:1})),p(E,{loading:_.value},{default:a(()=>[p(m,{align:`start`,fill:``},{default:a(()=>[p(g,{title:`角色列表`,size:`small`,style:{width:`220px`}},{default:a(()=>[p(m,{direction:`vertical`,fill:``},{default:a(()=>[(n(!0),f(u,null,h(x.value,e=>(n(),c(d,{key:e.role,type:S.value===e.role?`primary`:`secondary`,long:``,onClick:t=>O(e.role)},{default:a(()=>[o(t(e.label),1)]),_:2},1032,[`type`,`onClick`]))),128))]),_:1})]),_:1}),p(g,{title:`${T.value?.label||``} - 菜单权限`,size:`small`,style:{flex:`1`}},{extra:a(()=>[p(d,{type:`primary`,disabled:!y.value,loading:v.value,onClick:A},{default:a(()=>[...r[2]||=[o(`保存当前角色`,-1)]]),_:1},8,[`disabled`,`loading`])]),default:a(()=>[p(b,{checkable:``,"block-node":``,data:w.value,"checked-keys":C.value,"default-expand-all":!0,onCheck:k},null,8,[`data`,`checked-keys`])]),_:1},8,[`title`])]),_:1})]),_:1},8,[`loading`])]),_:1})}}});export{_ as default};
import{n as e}from"./axios-CiYFffbI.js";import{I as t,N as n,V as r,Y as i,_ as a,d as o,f as s,i as c,it as l,kt as u,l as d,p as f,v as p,y as m,z as h}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{n as g}from"./index-AJjRNeYZ.js";var _=m({__name:`Roles`,setup(m){let _=l(!1),v=l(!1),y=l(!1),b=l([]),x=l([]),S=l(`super_admin`),C=l([]),w=d(()=>{let e=new Map;b.value.forEach(t=>e.set(t.id,{key:t.id,title:t.name,children:[]}));let t=[];return b.value.forEach(n=>{let r=e.get(n.id);n.parent_id>0&&e.has(n.parent_id)?e.get(n.parent_id).children.push(r):t.push(r)}),t}),T=d(()=>x.value.find(e=>e.role===S.value));async function E(){let{data:e}=await g.get(`/me`);y.value=e?.role===`super_admin`}async function D(){_.value=!0;try{let{data:e}=await g.get(`/role-menu-permissions`);b.value=e.menus||[],x.value=e.roles||[];let t=x.value[0];t&&(S.value=t.role,C.value=[...t.menu_ids||[]])}catch(t){e.error(t?.response?.data?.message??`加载角色菜单权限失败`)}finally{_.value=!1}}function O(e){S.value=e,C.value=[...x.value.find(t=>t.role===e)?.menu_ids||[]]}function k(e){C.value=e.map(e=>Number(e))}async function A(){if(y.value){v.value=!0;try{await g.put(`/role-menu-permissions/${S.value}`,{menu_ids:C.value});let t=x.value.find(e=>e.role===S.value);t&&(t.menu_ids=[...C.value]),e.success(`角色菜单权限保存成功`)}catch(t){e.error(t?.response?.data?.message??`保存失败`)}finally{v.value=!1}}}return n(async()=>{await E(),await D()}),(e,n)=>{let l=r(`a-alert`),d=r(`a-button`),m=r(`a-space`),g=r(`a-card`),b=r(`a-tree`),E=r(`a-spin`);return t(),o(g,{title:`用户与权限 / 角色管理(菜单权限)`},{default:i(()=>[p(l,{style:{"margin-bottom":`12px`}},{default:i(()=>[...n[0]||=[a(` 当前仅控制“每个角色可查看哪些菜单”;接口级细粒度权限后续可继续扩展。 `,-1)]]),_:1}),y.value?s(``,!0):(t(),o(l,{key:0,type:`info`,style:{"margin-bottom":`12px`}},{default:i(()=>[...n[1]||=[a(` 当前为只读模式,仅超级管理员可以修改角色菜单权限。 `,-1)]]),_:1})),p(E,{loading:_.value},{default:i(()=>[p(m,{align:`start`,fill:``},{default:i(()=>[p(g,{title:`角色列表`,size:`small`,style:{width:`220px`}},{default:i(()=>[p(m,{direction:`vertical`,fill:``},{default:i(()=>[(t(!0),f(c,null,h(x.value,e=>(t(),o(d,{key:e.role,type:S.value===e.role?`primary`:`secondary`,long:``,onClick:t=>O(e.role)},{default:i(()=>[a(u(e.label),1)]),_:2},1032,[`type`,`onClick`]))),128))]),_:1})]),_:1}),p(g,{title:`${T.value?.label||``} - 菜单权限`,size:`small`,style:{flex:`1`}},{extra:i(()=>[p(d,{type:`primary`,disabled:!y.value,loading:v.value,onClick:A},{default:i(()=>[...n[2]||=[a(`保存当前角色`,-1)]]),_:1},8,[`disabled`,`loading`])]),default:i(()=>[p(b,{checkable:``,"block-node":``,data:w.value,"checked-keys":C.value,"default-expand-all":!0,onCheck:k},null,8,[`data`,`checked-keys`])]),_:1},8,[`title`])]),_:1})]),_:1},8,[`loading`])]),_:1})}}});export{_ as default};

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`SystemLogs`,setup(n){return(n,i)=>(e(),t(r,{title:`系统设置 / 系统日志`}))}});export{i as default};

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`SystemLogs`,setup(n){return(n,i)=>(e(),t(r,{title:`系统设置 / 系统日志`}))}});export{i as default};

@ -0,0 +1 @@
.tg-venue-block[data-v-1f2c168d]{width:100%}.tg-venue-block__add[data-v-1f2c168d]{margin-bottom:10px}.tg-venue-table-scroll[data-v-1f2c168d]{box-sizing:border-box;width:100%;max-width:100%;overflow-x:auto}.tg-venue-table[data-v-1f2c168d]{width:100%;min-width:0}.tg-venue-quota-input[data-v-1f2c168d] .arco-input-wrapper{min-width:120px}.tg-venue-actions[data-v-1f2c168d]{box-sizing:border-box;flex-wrap:nowrap;justify-content:center;align-items:center;gap:0;width:100%;display:inline-flex}.tg-venue-actions[data-v-1f2c168d] .arco-btn-size-small{padding-left:4px;padding-right:4px}.tg-list-actions[data-v-1f2c168d]{flex-wrap:wrap;justify-content:center;row-gap:2px;max-width:100%}.activity-cover-carousel-wrap[data-v-1f2c168d]{flex-wrap:wrap;align-items:flex-start;gap:20px;width:100%;display:flex}.activity-cover-carousel-row__col[data-v-1f2c168d]{flex:320px;min-width:min(100%,320px)}.activity-cover-carousel-row__sub[data-v-1f2c168d]{color:var(--color-text-1);margin-bottom:8px;font-weight:500}.activity-cover-thumb[data-v-1f2c168d]{object-fit:cover;cursor:zoom-in;border:1px solid #e5e6eb;border-radius:4px;width:120px;height:70px}.activity-gallery-grid[data-v-1f2c168d]{flex-wrap:wrap;align-items:flex-start;gap:12px;width:100%;display:flex}.activity-gallery-item[data-v-1f2c168d]{flex-direction:column;align-items:flex-start;gap:8px;display:flex}.activity-gallery-thumb[data-v-1f2c168d]{object-fit:cover;cursor:zoom-in;border:1px solid #e5e6eb;border-radius:4px;width:120px;height:80px}.activity-gallery-thumb--video[data-v-1f2c168d]{cursor:default}.activity-address-coord-row[data-v-1f2c168d]{flex-wrap:wrap;align-items:center;gap:12px;width:100%;display:flex}.activity-address-coord-row__address[data-v-1f2c168d]{flex:45%;min-width:220px;max-width:100%}.activity-address-coord-row__lng[data-v-1f2c168d],.activity-address-coord-row__lat[data-v-1f2c168d]{flex:180px;width:200px;min-width:160px}.activity-address-coord-row__map[data-v-1f2c168d]{flex-shrink:0}.tg-venue-contact-row[data-v-1f2c168d]{flex-wrap:wrap;align-items:flex-start;gap:16px;width:100%;display:flex}.tg-venue-contact-row__col[data-v-1f2c168d]{flex:200px;min-width:180px;max-width:100%}.tg-venue-contact-row__sub[data-v-1f2c168d]{color:var(--color-text-1);margin-bottom:8px;font-size:13px;font-weight:500}

@ -1 +0,0 @@
.tg-form[data-v-aa7611c1] .arco-form-item{margin-bottom:10px}.tg-form__full[data-v-aa7611c1]{width:100%}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
import{n as e}from"./axios-CiYFffbI.js";import{I as t,N as n,V as r,Y as i,_ as a,d as o,it as s,kt as c,nt as l,ut as u,v as d,y as f}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{n as p}from"./index-AJjRNeYZ.js";import{n as m,t as h}from"./datetime-3T8f3S0H.js";import{t as g}from"./listTable-DXuZ0yk8.js";import{t as _}from"./bookingType-CGtYk0HZ.js";import{t as v}from"./reservationStatus-DfEgE0qr.js";var y=f({__name:`TicketGrabRegistrations`,setup(f){let y=s(!1),b=s(`all`),x=s(``),S=s([]),C=l({current:1,pageSize:10,total:0}),w=s([]);async function T(){y.value=!0;try{let{data:e}=await p.get(`/activity-registrations`,{params:{reservation_kind:`ticket_grab`,status:b.value,keyword:x.value||void 0,start_date:S.value?.[0]||void 0,end_date:S.value?.[1]||void 0,page:C.current,page_size:C.pageSize}});w.value=e.data,C.total=e.total}catch(t){e.error(t?.response?.data?.message??`加载失败`)}finally{y.value=!1}}function E(e){C.current=e,T()}function D(e){C.pageSize=e,C.current=1,T()}return n(T),(e,n)=>{let s=r(`a-radio`),l=r(`a-radio-group`),f=r(`a-input`),p=r(`a-range-picker`),O=r(`a-button`),k=r(`a-space`),A=r(`a-table-column`),j=r(`a-tag`),M=r(`a-table`),N=r(`a-card`);return t(),o(N,{title:`抢票管理 / 抢票报名`,bordered:!1},{default:i(()=>[d(k,{direction:`vertical`,fill:``},{default:i(()=>[d(k,{wrap:``,size:12},{default:i(()=>[d(l,{modelValue:b.value,"onUpdate:modelValue":n[0]||=e=>b.value=e,type:`button`,size:`small`,onChange:T},{default:i(()=>[d(s,{value:`all`},{default:i(()=>[...n[5]||=[a(`全部`,-1)]]),_:1}),d(s,{value:`pending`},{default:i(()=>[...n[6]||=[a(`待核销`,-1)]]),_:1}),d(s,{value:`verified`},{default:i(()=>[...n[7]||=[a(`已核销`,-1)]]),_:1}),d(s,{value:`cancelled`},{default:i(()=>[...n[8]||=[a(`已取消`,-1)]]),_:1}),d(s,{value:`expired`},{default:i(()=>[...n[9]||=[a(`已过期`,-1)]]),_:1})]),_:1},8,[`modelValue`]),d(f,{modelValue:x.value,"onUpdate:modelValue":n[1]||=e=>x.value=e,placeholder:`姓名 / 身份证 / token`,"allow-clear":``,style:{width:`220px`}},null,8,[`modelValue`]),d(p,{modelValue:S.value,"onUpdate:modelValue":n[2]||=e=>S.value=e,style:{width:`260px`}},null,8,[`modelValue`]),d(O,{type:`primary`,onClick:n[3]||=()=>{C.current=1,T()}},{default:i(()=>[...n[10]||=[a(` 查询 `,-1)]]),_:1}),d(O,{onClick:n[4]||=()=>{b.value=`all`,x.value=``,S.value=[],C.current=1,T()}},{default:i(()=>[...n[11]||=[a(` 重置 `,-1)]]),_:1}),d(O,{onClick:T},{default:i(()=>[...n[12]||=[a(`刷新`,-1)]]),_:1})]),_:1}),d(M,{scroll:{x:u(g)},data:w.value,loading:y.value,"row-key":`id`,pagination:{current:C.current,pageSize:C.pageSize,total:C.total,showTotal:!0,onChange:E,onPageSizeChange:D}},{columns:i(()=>[d(A,{title:`ID`,"data-index":`id`,width:80}),d(A,{title:`抢票活动`,width:200,ellipsis:!0,tooltip:!0},{cell:i(({record:e})=>[a(c(e.ticket_grab_event?.title??`-`),1)]),_:1}),d(A,{title:`场馆`,width:160,ellipsis:!0,tooltip:!0},{cell:i(({record:e})=>[a(c(e.venue?.name??`-`),1)]),_:1}),d(A,{title:`姓名`,"data-index":`visitor_name`,width:100}),d(A,{title:`身份证`,"data-index":`id_card`,width:180,ellipsis:!0,tooltip:!0}),d(A,{title:`入馆日`,width:120},{cell:i(({record:e})=>[a(c(e.entry_date?u(m)(String(e.entry_date)):`-`),1)]),_:1}),d(A,{title:`预约类型`,width:100},{cell:i(({record:e})=>[a(c(u(_)(e.booking_type,e.ticket_count)),1)]),_:1}),d(A,{title:`票数`,width:80},{cell:i(({record:e})=>[a(c(e.ticket_count??1),1)]),_:1}),d(A,{title:`状态`,width:100},{cell:i(({record:e})=>[d(j,{color:e.status===`verified`?`green`:e.status===`pending`?`arcoblue`:`gray`},{default:i(()=>[a(c(u(v)(e.status)),1)]),_:2},1032,[`color`])]),_:1}),d(A,{title:`下单时间`,width:170},{cell:i(({record:e})=>[a(c(u(h)(e.created_at)),1)]),_:1})]),_:1},8,[`scroll`,`data`,`loading`,`pagination`])]),_:1})]),_:1})}}});export{y as default};

@ -1 +0,0 @@
import{n as e}from"./axios-Cze8nXLL.js";import{At as t,I as n,N as r,V as i,X as a,_ as o,at as s,d as c,dt as l,rt as u,v as d,y as f}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{n as p}from"./index-qyPO_3-6.js";import{n as m,t as h}from"./datetime-3T8f3S0H.js";import{t as g}from"./listTable-DXuZ0yk8.js";import{t as _}from"./bookingType-CGtYk0HZ.js";import{t as v}from"./reservationStatus-DfEgE0qr.js";var y=f({__name:`TicketGrabRegistrations`,setup(f){let y=s(!1),b=s(`all`),x=s(``),S=s([]),C=u({current:1,pageSize:10,total:0}),w=s([]);async function T(){y.value=!0;try{let{data:e}=await p.get(`/activity-registrations`,{params:{reservation_kind:`ticket_grab`,status:b.value,keyword:x.value||void 0,start_date:S.value?.[0]||void 0,end_date:S.value?.[1]||void 0,page:C.current,page_size:C.pageSize}});w.value=e.data,C.total=e.total}catch(t){e.error(t?.response?.data?.message??`加载失败`)}finally{y.value=!1}}function E(e){C.current=e,T()}function D(e){C.pageSize=e,C.current=1,T()}return r(T),(e,r)=>{let s=i(`a-radio`),u=i(`a-radio-group`),f=i(`a-input`),p=i(`a-range-picker`),O=i(`a-button`),k=i(`a-space`),A=i(`a-table-column`),j=i(`a-tag`),M=i(`a-table`),N=i(`a-card`);return n(),c(N,{title:`抢票管理 / 抢票报名`,bordered:!1},{default:a(()=>[d(k,{direction:`vertical`,fill:``},{default:a(()=>[d(k,{wrap:``,size:12},{default:a(()=>[d(u,{modelValue:b.value,"onUpdate:modelValue":r[0]||=e=>b.value=e,type:`button`,size:`small`,onChange:T},{default:a(()=>[d(s,{value:`all`},{default:a(()=>[...r[5]||=[o(`全部`,-1)]]),_:1}),d(s,{value:`pending`},{default:a(()=>[...r[6]||=[o(`待核销`,-1)]]),_:1}),d(s,{value:`verified`},{default:a(()=>[...r[7]||=[o(`已核销`,-1)]]),_:1}),d(s,{value:`cancelled`},{default:a(()=>[...r[8]||=[o(`已取消`,-1)]]),_:1}),d(s,{value:`expired`},{default:a(()=>[...r[9]||=[o(`已过期`,-1)]]),_:1})]),_:1},8,[`modelValue`]),d(f,{modelValue:x.value,"onUpdate:modelValue":r[1]||=e=>x.value=e,placeholder:`姓名 / 身份证 / token`,"allow-clear":``,style:{width:`220px`}},null,8,[`modelValue`]),d(p,{modelValue:S.value,"onUpdate:modelValue":r[2]||=e=>S.value=e,style:{width:`260px`}},null,8,[`modelValue`]),d(O,{type:`primary`,onClick:r[3]||=()=>{C.current=1,T()}},{default:a(()=>[...r[10]||=[o(` 查询 `,-1)]]),_:1}),d(O,{onClick:r[4]||=()=>{b.value=`all`,x.value=``,S.value=[],C.current=1,T()}},{default:a(()=>[...r[11]||=[o(` 重置 `,-1)]]),_:1}),d(O,{onClick:T},{default:a(()=>[...r[12]||=[o(`刷新`,-1)]]),_:1})]),_:1}),d(M,{scroll:{x:l(g)},data:w.value,loading:y.value,"row-key":`id`,pagination:{current:C.current,pageSize:C.pageSize,total:C.total,showTotal:!0,onChange:E,onPageSizeChange:D}},{columns:a(()=>[d(A,{title:`ID`,"data-index":`id`,width:80}),d(A,{title:`抢票活动`,width:200,ellipsis:!0,tooltip:!0},{cell:a(({record:e})=>[o(t(e.ticket_grab_event?.title??`-`),1)]),_:1}),d(A,{title:`场馆`,width:160,ellipsis:!0,tooltip:!0},{cell:a(({record:e})=>[o(t(e.venue?.name??`-`),1)]),_:1}),d(A,{title:`姓名`,"data-index":`visitor_name`,width:100}),d(A,{title:`身份证`,"data-index":`id_card`,width:180,ellipsis:!0,tooltip:!0}),d(A,{title:`入馆日`,width:120},{cell:a(({record:e})=>[o(t(e.entry_date?l(m)(String(e.entry_date)):`-`),1)]),_:1}),d(A,{title:`预约类型`,width:100},{cell:a(({record:e})=>[o(t(l(_)(e.booking_type,e.ticket_count)),1)]),_:1}),d(A,{title:`票数`,width:80},{cell:a(({record:e})=>[o(t(e.ticket_count??1),1)]),_:1}),d(A,{title:`状态`,width:100},{cell:a(({record:e})=>[d(j,{color:e.status===`verified`?`green`:e.status===`pending`?`arcoblue`:`gray`},{default:a(()=>[o(t(l(v)(e.status)),1)]),_:2},1032,[`color`])]),_:1}),d(A,{title:`下单时间`,width:170},{cell:a(({record:e})=>[o(t(l(h)(e.created_at)),1)]),_:1})]),_:1},8,[`scroll`,`data`,`loading`,`pagination`])]),_:1})]),_:1})}}});export{y as default};

@ -1 +0,0 @@
import{n as e}from"./axios-Cze8nXLL.js";import{At as t,I as n,N as r,V as i,X as a,_ as o,at as s,d as c,dt as l,u,v as d,y as f}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{n as p,o as m}from"./index-qyPO_3-6.js";import{n as h,t as g}from"./datetime-3T8f3S0H.js";import{t as _}from"./listTable-DXuZ0yk8.js";import{t as v}from"./bookingType-CGtYk0HZ.js";import{t as y}from"./reservationStatus-DfEgE0qr.js";var b={class:`verify-list-toolbar`},x=m(f({__name:`TicketGrabVerify`,setup(f){let m=s(!1),x=s([]),S=s(``),C=s(!1),w=s(`all`),T=s(``),E=s([]);async function D(){m.value=!0;try{let e={status:w.value,keyword:T.value||void 0,reservation_kind:`ticket_grab`};E.value?.length===2&&(e.start_date=E.value[0],e.end_date=E.value[1],e.date_field=`entry_date`);let{data:t}=await p.get(`/reservations`,{params:e});x.value=t}catch(t){e.error(t?.response?.data?.message??`加载预约列表失败`)}finally{m.value=!1}}function O(){D()}function k(){w.value=`all`,T.value=``,E.value=[],D()}async function A(){if(!S.value){e.warning(`请输入二维码 token`);return}C.value=!0;try{await p.post(`/reservations/verify`,{qr_token:S.value}),e.success(`核销成功`),S.value=``,await D()}catch(t){e.error(t?.response?.data?.message??`核销失败`)}finally{C.value=!1}}return r(D),(e,r)=>{let s=i(`a-alert`),f=i(`a-input`),p=i(`a-button`),j=i(`a-space`),M=i(`a-radio`),N=i(`a-radio-group`),P=i(`a-range-picker`),F=i(`a-table-column`),I=i(`a-tag`),L=i(`a-table`),R=i(`a-card`);return n(),c(R,{title:`抢票管理 / 抢票核销`,bordered:!1},{default:a(()=>[d(j,{direction:`vertical`,fill:``},{default:a(()=>[d(s,null,{default:a(()=>[...r[4]||=[o(`抢票预约按「入馆日」为当天方可核销。输入二维码 token 核销。`,-1)]]),_:1}),d(j,{wrap:``,size:12},{default:a(()=>[d(f,{modelValue:S.value,"onUpdate:modelValue":r[0]||=e=>S.value=e,style:{width:`min(100%, 420px)`},placeholder:`请输入二维码 token`,"allow-clear":``},null,8,[`modelValue`]),d(p,{type:`primary`,loading:C.value,onClick:A},{default:a(()=>[...r[5]||=[o(`立即核销`,-1)]]),_:1},8,[`loading`])]),_:1}),u(`div`,b,[d(j,{wrap:``,size:12},{default:a(()=>[d(N,{modelValue:w.value,"onUpdate:modelValue":r[1]||=e=>w.value=e,type:`button`,size:`small`,onChange:D},{default:a(()=>[d(M,{value:`all`},{default:a(()=>[...r[6]||=[o(`全部`,-1)]]),_:1}),d(M,{value:`pending`},{default:a(()=>[...r[7]||=[o(`待核销`,-1)]]),_:1}),d(M,{value:`verified`},{default:a(()=>[...r[8]||=[o(`已核销`,-1)]]),_:1}),d(M,{value:`cancelled`},{default:a(()=>[...r[9]||=[o(`已取消`,-1)]]),_:1}),d(M,{value:`expired`},{default:a(()=>[...r[10]||=[o(`已过期`,-1)]]),_:1})]),_:1},8,[`modelValue`]),d(f,{modelValue:T.value,"onUpdate:modelValue":r[2]||=e=>T.value=e,placeholder:`姓名 / 手机 / 身份证 / token`,"allow-clear":``,style:{width:`240px`}},null,8,[`modelValue`]),d(P,{modelValue:E.value,"onUpdate:modelValue":r[3]||=e=>E.value=e,style:{width:`260px`}},null,8,[`modelValue`]),d(p,{type:`primary`,onClick:O},{default:a(()=>[...r[11]||=[o(`查询`,-1)]]),_:1}),d(p,{onClick:k},{default:a(()=>[...r[12]||=[o(`重置`,-1)]]),_:1}),d(p,{onClick:D},{default:a(()=>[...r[13]||=[o(`刷新列表`,-1)]]),_:1})]),_:1})]),d(L,{class:`list-data-table verify-table`,scroll:{x:l(_)},data:x.value,loading:m.value,"row-key":`id`,pagination:{pageSize:10,showTotal:!0}},{columns:a(()=>[d(F,{title:`ID`,"data-index":`id`,width:88}),d(F,{title:`预约场次`,width:220,ellipsis:!0,tooltip:!0},{cell:a(({record:e})=>[o(t(e.ticket_grab_event?.title??`-`),1)]),_:1}),d(F,{title:`场馆`,width:180,ellipsis:!0,tooltip:!0},{cell:a(({record:e})=>[o(t(e.venue?.name??`-`),1)]),_:1}),d(F,{title:`姓名`,"data-index":`visitor_name`,width:100}),d(F,{title:`身份证`,"data-index":`id_card`,width:180,ellipsis:!0,tooltip:!0}),d(F,{title:`手机号`,"data-index":`visitor_phone`,width:120}),d(F,{title:`预约类型`,width:100},{cell:a(({record:e})=>[o(t(l(v)(e.booking_type,e.ticket_count)),1)]),_:1}),d(F,{title:`场次时间`,width:140},{cell:a(({record:e})=>[o(t(e.entry_date?l(h)(String(e.entry_date)):`-`),1)]),_:1}),d(F,{title:`状态`,width:100},{cell:a(({record:e})=>[d(I,{color:e.status===`verified`?`green`:e.status===`pending`?`arcoblue`:e.status===`expired`?`orange`:`gray`},{default:a(()=>[o(t(l(y)(e.status)),1)]),_:2},1032,[`color`])]),_:1}),d(F,{title:`预约时间`,width:175},{cell:a(({record:e})=>[o(t(l(g)(e.created_at)),1)]),_:1}),d(F,{title:`核销时间`,width:175},{cell:a(({record:e})=>[o(t(l(g)(e.verified_at)),1)]),_:1}),d(F,{title:`二维码 token`,"data-index":`qr_token`,width:200,ellipsis:!0,tooltip:!0,fixed:`right`})]),_:1},8,[`scroll`,`data`,`loading`])]),_:1})]),_:1})}}}),[[`__scopeId`,`data-v-2b27085e`]]);export{x as default};

@ -0,0 +1 @@
import{n as e}from"./axios-CiYFffbI.js";import{I as t,N as n,V as r,Y as i,_ as a,d as o,it as s,kt as c,u as l,ut as u,v as d,y as f}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{n as p,o as m}from"./index-AJjRNeYZ.js";import{n as h,t as g}from"./datetime-3T8f3S0H.js";import{t as _}from"./listTable-DXuZ0yk8.js";import{t as v}from"./bookingType-CGtYk0HZ.js";import{t as y}from"./reservationStatus-DfEgE0qr.js";var b={class:`verify-list-toolbar`},x=m(f({__name:`TicketGrabVerify`,setup(f){let m=s(!1),x=s([]),S=s(``),C=s(!1),w=s(`all`),T=s(``),E=s([]);async function D(){m.value=!0;try{let e={status:w.value,keyword:T.value||void 0,reservation_kind:`ticket_grab`};E.value?.length===2&&(e.start_date=E.value[0],e.end_date=E.value[1],e.date_field=`entry_date`);let{data:t}=await p.get(`/reservations`,{params:e});x.value=t}catch(t){e.error(t?.response?.data?.message??`加载预约列表失败`)}finally{m.value=!1}}function O(){D()}function k(){w.value=`all`,T.value=``,E.value=[],D()}async function A(){if(!S.value){e.warning(`请输入二维码 token`);return}C.value=!0;try{await p.post(`/reservations/verify`,{qr_token:S.value}),e.success(`核销成功`),S.value=``,await D()}catch(t){e.error(t?.response?.data?.message??`核销失败`)}finally{C.value=!1}}return n(D),(e,n)=>{let s=r(`a-alert`),f=r(`a-input`),p=r(`a-button`),j=r(`a-space`),M=r(`a-radio`),N=r(`a-radio-group`),P=r(`a-range-picker`),F=r(`a-table-column`),I=r(`a-tag`),L=r(`a-table`),R=r(`a-card`);return t(),o(R,{title:`抢票管理 / 抢票核销`,bordered:!1},{default:i(()=>[d(j,{direction:`vertical`,fill:``},{default:i(()=>[d(s,null,{default:i(()=>[...n[4]||=[a(`抢票预约按「入馆日」为当天方可核销。输入二维码 token 核销。`,-1)]]),_:1}),d(j,{wrap:``,size:12},{default:i(()=>[d(f,{modelValue:S.value,"onUpdate:modelValue":n[0]||=e=>S.value=e,style:{width:`min(100%, 420px)`},placeholder:`请输入二维码 token`,"allow-clear":``},null,8,[`modelValue`]),d(p,{type:`primary`,loading:C.value,onClick:A},{default:i(()=>[...n[5]||=[a(`立即核销`,-1)]]),_:1},8,[`loading`])]),_:1}),l(`div`,b,[d(j,{wrap:``,size:12},{default:i(()=>[d(N,{modelValue:w.value,"onUpdate:modelValue":n[1]||=e=>w.value=e,type:`button`,size:`small`,onChange:D},{default:i(()=>[d(M,{value:`all`},{default:i(()=>[...n[6]||=[a(`全部`,-1)]]),_:1}),d(M,{value:`pending`},{default:i(()=>[...n[7]||=[a(`待核销`,-1)]]),_:1}),d(M,{value:`verified`},{default:i(()=>[...n[8]||=[a(`已核销`,-1)]]),_:1}),d(M,{value:`cancelled`},{default:i(()=>[...n[9]||=[a(`已取消`,-1)]]),_:1}),d(M,{value:`expired`},{default:i(()=>[...n[10]||=[a(`已过期`,-1)]]),_:1})]),_:1},8,[`modelValue`]),d(f,{modelValue:T.value,"onUpdate:modelValue":n[2]||=e=>T.value=e,placeholder:`姓名 / 手机 / 身份证 / token`,"allow-clear":``,style:{width:`240px`}},null,8,[`modelValue`]),d(P,{modelValue:E.value,"onUpdate:modelValue":n[3]||=e=>E.value=e,style:{width:`260px`}},null,8,[`modelValue`]),d(p,{type:`primary`,onClick:O},{default:i(()=>[...n[11]||=[a(`查询`,-1)]]),_:1}),d(p,{onClick:k},{default:i(()=>[...n[12]||=[a(`重置`,-1)]]),_:1}),d(p,{onClick:D},{default:i(()=>[...n[13]||=[a(`刷新列表`,-1)]]),_:1})]),_:1})]),d(L,{class:`list-data-table verify-table`,scroll:{x:u(_)},data:x.value,loading:m.value,"row-key":`id`,pagination:{pageSize:10,showTotal:!0}},{columns:i(()=>[d(F,{title:`ID`,"data-index":`id`,width:88}),d(F,{title:`预约场次`,width:220,ellipsis:!0,tooltip:!0},{cell:i(({record:e})=>[a(c(e.ticket_grab_event?.title??`-`),1)]),_:1}),d(F,{title:`场馆`,width:180,ellipsis:!0,tooltip:!0},{cell:i(({record:e})=>[a(c(e.venue?.name??`-`),1)]),_:1}),d(F,{title:`姓名`,"data-index":`visitor_name`,width:100}),d(F,{title:`身份证`,"data-index":`id_card`,width:180,ellipsis:!0,tooltip:!0}),d(F,{title:`手机号`,"data-index":`visitor_phone`,width:120}),d(F,{title:`预约类型`,width:100},{cell:i(({record:e})=>[a(c(u(v)(e.booking_type,e.ticket_count)),1)]),_:1}),d(F,{title:`场次时间`,width:140},{cell:i(({record:e})=>[a(c(e.entry_date?u(h)(String(e.entry_date)):`-`),1)]),_:1}),d(F,{title:`状态`,width:100},{cell:i(({record:e})=>[d(I,{color:e.status===`verified`?`green`:e.status===`pending`?`arcoblue`:e.status===`expired`?`orange`:`gray`},{default:i(()=>[a(c(u(y)(e.status)),1)]),_:2},1032,[`color`])]),_:1}),d(F,{title:`预约时间`,width:175},{cell:i(({record:e})=>[a(c(u(g)(e.created_at)),1)]),_:1}),d(F,{title:`核销时间`,width:175},{cell:i(({record:e})=>[a(c(u(g)(e.verified_at)),1)]),_:1}),d(F,{title:`二维码 token`,"data-index":`qr_token`,width:200,ellipsis:!0,tooltip:!0,fixed:`right`})]),_:1},8,[`scroll`,`data`,`loading`])]),_:1})]),_:1})}}}),[[`__scopeId`,`data-v-2b27085e`]]);export{x as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
.import-file-label[data-v-a15d676a]{cursor:pointer;display:inline-block}.import-file-input[data-v-a15d676a]{opacity:0;width:0;height:0;position:absolute;overflow:hidden}.import-file-btn[data-v-a15d676a]{border:1px solid var(--color-border-2);border-radius:var(--border-radius-small);padding:0 15px;line-height:32px;display:inline-block}.import-file-label:hover .import-file-btn[data-v-a15d676a]{border-color:rgb(var(--primary-6));color:rgb(var(--primary-6))}.venue-address-coord-row[data-v-a15d676a]{flex-wrap:wrap;align-items:center;gap:12px;width:100%;display:flex}.venue-address-coord-row__address[data-v-a15d676a]{flex:45%;min-width:320px;max-width:100%}.venue-address-coord-row__lng[data-v-a15d676a],.venue-address-coord-row__lat[data-v-a15d676a]{flex:180px;width:200px;min-width:180px}.venue-address-coord-row__map[data-v-a15d676a]{flex-shrink:0}.venue-cover-carousel-wrap[data-v-a15d676a]{flex-wrap:wrap;align-items:flex-start;gap:20px;width:100%;display:flex}.venue-cover-carousel-row__col[data-v-a15d676a]{flex:320px;min-width:min(100%,320px)}.venue-cover-carousel-row__sub[data-v-a15d676a]{color:var(--color-text-1);margin-bottom:8px;font-weight:500}.venue-gallery-grid[data-v-a15d676a]{flex-wrap:wrap;align-items:flex-start;gap:12px;width:100%;display:flex}.venue-gallery-item[data-v-a15d676a]{flex-direction:column;align-items:flex-start;gap:8px;display:flex}.venue-gallery-thumb[data-v-a15d676a]{object-fit:cover;cursor:zoom-in;border:1px solid #e5e6eb;border-radius:4px;width:80px;height:50px}.venue-gallery-thumb--video[data-v-a15d676a]{display:block}

@ -1 +0,0 @@
.import-file-label[data-v-57b63ab2]{cursor:pointer;display:inline-block}.import-file-input[data-v-57b63ab2]{opacity:0;width:0;height:0;position:absolute;overflow:hidden}.import-file-btn[data-v-57b63ab2]{border:1px solid var(--color-border-2);border-radius:var(--border-radius-small);padding:0 15px;line-height:32px;display:inline-block}.import-file-label:hover .import-file-btn[data-v-57b63ab2]{border-color:rgb(var(--primary-6));color:rgb(var(--primary-6))}.venue-address-coord-row[data-v-57b63ab2]{flex-wrap:wrap;align-items:center;gap:12px;width:100%;display:flex}.venue-address-coord-row__address[data-v-57b63ab2]{flex:45%;min-width:320px;max-width:100%}.venue-address-coord-row__lng[data-v-57b63ab2],.venue-address-coord-row__lat[data-v-57b63ab2]{flex:180px;width:200px;min-width:180px}.venue-address-coord-row__map[data-v-57b63ab2]{flex-shrink:0}.venue-cover-carousel-wrap[data-v-57b63ab2]{flex-wrap:wrap;align-items:flex-start;gap:20px;width:100%;display:flex}.venue-cover-carousel-row__col[data-v-57b63ab2]{flex:320px;min-width:min(100%,320px)}.venue-cover-carousel-row__sub[data-v-57b63ab2]{color:var(--color-text-1);margin-bottom:8px;font-weight:500}.venue-gallery-grid[data-v-57b63ab2]{flex-wrap:wrap;align-items:flex-start;gap:12px;width:100%;display:flex}.venue-gallery-item[data-v-57b63ab2]{flex-direction:column;align-items:flex-start;gap:8px;display:flex}.venue-gallery-thumb[data-v-57b63ab2]{object-fit:cover;cursor:zoom-in;border:1px solid #e5e6eb;border-radius:4px;width:80px;height:50px}.venue-gallery-thumb--video[data-v-57b63ab2]{display:block}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
import{n as e}from"./axios-Cze8nXLL.js";import{I as t,V as n,X as r,_ as i,at as a,p as o,rt as s,u as c,v as l,y as u}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{i as d,o as f,r as p}from"./index-qyPO_3-6.js";import{n as m,t as h}from"./h5Http-PQ0NZPES.js";var g={class:`m-verify-page`},_={class:`m-verify-card`},v=f(u({__name:`VerifyLogin`,setup(u){let f=d(),v=p(),y=a(!1),b=s({username:``,password:``});function x(){return v.path.startsWith(`/m/`)?`/m/verify`:`/h5/verify/scan`}async function S(){y.value=!0;try{let{data:t}=await m.post(`/auth/login`,{...b,client:`h5_verify`});localStorage.setItem(h,t.token),localStorage.setItem(`${h}_saved_at`,String(Date.now())),e.success(`登录成功`),f.replace(x())}catch(t){e.error(t?.response?.data?.message??`登录失败`)}finally{y.value=!1}}return(e,a)=>{let s=n(`a-input`),u=n(`a-form-item`),d=n(`a-input-password`),f=n(`a-button`),p=n(`a-form`);return t(),o(`div`,g,[a[4]||=c(`div`,{class:`m-verify-hero`},[c(`div`,{class:`m-verify-title`},`苏州市科普场馆地图`),c(`div`,{class:`m-verify-sub`},`移动端核销登录`)],-1),c(`div`,_,[l(p,{model:b,layout:`vertical`,onSubmitSuccess:S},{default:r(()=>[l(u,{label:`用户名`},{default:r(()=>[l(s,{modelValue:b.username,"onUpdate:modelValue":a[0]||=e=>b.username=e,placeholder:`请输入账号`,size:`large`,"allow-clear":``},null,8,[`modelValue`])]),_:1}),l(u,{label:`密码`},{default:r(()=>[l(d,{modelValue:b.password,"onUpdate:modelValue":a[1]||=e=>b.password=e,placeholder:`请输入密码`,size:`large`,"allow-clear":``},null,8,[`modelValue`])]),_:1}),l(f,{type:`primary`,long:``,size:`large`,loading:y.value,onClick:S},{default:r(()=>[...a[2]||=[i(`登录`,-1)]]),_:1},8,[`loading`])]),_:1},8,[`model`]),a[3]||=c(`p`,{class:`m-verify-tip`},`登录状态将保持较长时间;若已失效会自动回到本页。`,-1)])])}}}),[[`__scopeId`,`data-v-b762ebdf`]]);export{v as default};
import{n as e}from"./axios-CiYFffbI.js";import{I as t,V as n,Y as r,_ as i,it as a,nt as o,p as s,u as c,v as l,y as u}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{i as d,o as f,r as p}from"./index-AJjRNeYZ.js";import{n as m,t as h}from"./h5Http-B_zh1Fd1.js";var g={class:`m-verify-page`},_={class:`m-verify-card`},v=f(u({__name:`VerifyLogin`,setup(u){let f=d(),v=p(),y=a(!1),b=o({username:``,password:``});function x(){return v.path.startsWith(`/m/`)?`/m/verify`:`/h5/verify/scan`}async function S(){y.value=!0;try{let{data:t}=await m.post(`/auth/login`,{...b,client:`h5_verify`});localStorage.setItem(h,t.token),localStorage.setItem(`${h}_saved_at`,String(Date.now())),e.success(`登录成功`),f.replace(x())}catch(t){e.error(t?.response?.data?.message??`登录失败`)}finally{y.value=!1}}return(e,a)=>{let o=n(`a-input`),u=n(`a-form-item`),d=n(`a-input-password`),f=n(`a-button`),p=n(`a-form`);return t(),s(`div`,g,[a[4]||=c(`div`,{class:`m-verify-hero`},[c(`div`,{class:`m-verify-title`},`苏州市科普场馆地图`),c(`div`,{class:`m-verify-sub`},`移动端核销登录`)],-1),c(`div`,_,[l(p,{model:b,layout:`vertical`,onSubmitSuccess:S},{default:r(()=>[l(u,{label:`用户名`},{default:r(()=>[l(o,{modelValue:b.username,"onUpdate:modelValue":a[0]||=e=>b.username=e,placeholder:`请输入账号`,size:`large`,"allow-clear":``},null,8,[`modelValue`])]),_:1}),l(u,{label:`密码`},{default:r(()=>[l(d,{modelValue:b.password,"onUpdate:modelValue":a[1]||=e=>b.password=e,placeholder:`请输入密码`,size:`large`,"allow-clear":``},null,8,[`modelValue`])]),_:1}),l(f,{type:`primary`,long:``,size:`large`,loading:y.value,onClick:S},{default:r(()=>[...a[2]||=[i(`登录`,-1)]]),_:1},8,[`loading`])]),_:1},8,[`model`]),a[3]||=c(`p`,{class:`m-verify-tip`},`登录状态将保持较长时间;若已失效会自动回到本页。`,-1)])])}}}),[[`__scopeId`,`data-v-b762ebdf`]]);export{v as default};

@ -1 +0,0 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CP0MNZrl.js";import{t as r}from"./PagePlaceholder-DIUY_BUW.js";var i=n({__name:`Wechat`,setup(n){return(n,i)=>(e(),t(r,{title:`系统设置 / 微信配置`}))}});export{i as default};

@ -0,0 +1 @@
import{I as e,d as t,y as n}from"./runtime-core.esm-bundler-CnFWH3R5.js";import{t as r}from"./PagePlaceholder-0iDIbJ_3.js";var i=n({__name:`Wechat`,setup(n){return(n,i)=>(e(),t(r,{title:`系统设置 / 微信配置`}))}});export{i as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
import{t as e}from"./axios-Cze8nXLL.js";var t=`szkp_h5_verify_token`,n=`https://szkp-map.langye.net/api`;function r(){let e=(window.location.pathname||``).includes(`/m/verify`)?`m/verify/login`:`h5/verify/login`,t=`/admin/`,n=t.endsWith(`/`)?t:`${t}/`;return`${window.location.origin}${n}${e}`}var i=e.create({baseURL:n,timeout:2e4});i.interceptors.request.use(e=>{let n=localStorage.getItem(t);return n&&(e.headers.Authorization=`Bearer ${n}`),e}),i.interceptors.response.use(e=>e,e=>{let n=e?.response?.status;if(n===401||n===403){localStorage.removeItem(t);let e=window.location.pathname||``;(e.includes(`/h5/verify`)||e.includes(`/m/verify`))&&window.location.replace(r())}return Promise.reject(e)});export{i as n,t};
import{t as e}from"./axios-CiYFffbI.js";var t=`szkp_h5_verify_token`,n=`https://szkp-map.langye.net/api`;function r(){let e=(window.location.pathname||``).includes(`/m/verify`)?`m/verify/login`:`h5/verify/login`,t=`/admin/`,n=t.endsWith(`/`)?t:`${t}/`;return`${window.location.origin}${n}${e}`}var i=e.create({baseURL:n,timeout:2e4});i.interceptors.request.use(e=>{let n=localStorage.getItem(t);return n&&(e.headers.Authorization=`Bearer ${n}`),e}),i.interceptors.response.use(e=>e,e=>{let n=e?.response?.status;if(n===401||n===403){localStorage.removeItem(t);let e=window.location.pathname||``;(e.includes(`/h5/verify`)||e.includes(`/m/verify`))&&window.location.replace(r())}return Promise.reject(e)});export{i as n,t};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -5,9 +5,9 @@
<link rel="icon" type="image/svg+xml" href="/admin/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>苏州市科普场馆地图管理后台</title>
<script type="module" crossorigin src="/admin/assets/index-qyPO_3-6.js"></script>
<link rel="modulepreload" crossorigin href="/admin/assets/runtime-core.esm-bundler-CP0MNZrl.js">
<link rel="modulepreload" crossorigin href="/admin/assets/axios-Cze8nXLL.js">
<script type="module" crossorigin src="/admin/assets/index-AJjRNeYZ.js"></script>
<link rel="modulepreload" crossorigin href="/admin/assets/runtime-core.esm-bundler-CnFWH3R5.js">
<link rel="modulepreload" crossorigin href="/admin/assets/axios-CiYFffbI.js">
<link rel="stylesheet" crossorigin href="/admin/assets/index-2DjvV_uU.css">
</head>
<body>

@ -1 +0,0 @@
import{h as t}from"./index-ccvrD15-.js";function n(n={}){const{include_ticket_grab:e,...i}=n,s={...i};return e&&(s.include_ticket_grab=1),t.get("/h5/activities",s,{withAuth:!1})}function e(n){return t.get(`/h5/activities/${n}`,{})}function i(n){return t.get(`/h5/ticket-grab-events/${n}`,{},{withAuth:!1})}function s(n,e){return t.get(`/h5/ticket-grab-events/${n}/booking-info`,{venue_id:e},{withAuth:!0})}function u(n,e){return t.post(`/h5/ticket-grab-events/${n}/reservations`,e,{withAuth:!0})}function r(n={}){const e={};return n.only_included_in_stats&&(e.only_included_in_stats=1),t.get("/h5/venues",e,{withAuth:!1})}function h(){return t.get("/h5/venue-dicts",{},{withAuth:!1})}function o(n){return t.get(`/h5/venues/${n}`,{},{withAuth:!1})}function c(n){return t.get(`/h5/study-tours/${n}`,{},{withAuth:!1})}function a(n={}){return t.get("/h5/study-tours",n,{withAuth:!1})}function g(n){return t.get(`/h5/activities/${n}/booking-info`,{},{withAuth:!0})}function f(n,e){return t.post(`/h5/activities/${n}/reservations`,e,{withAuth:!0})}function v(n){return t.get("/h5/my/reservations",{},{withAuth:!0})}function d(n,e){const i={};return e&&/^1\d{10}$/.test(e)&&(i.visitor_phone=e),t.get(`/h5/my/reservations/${n}`,i,{withAuth:!0})}function w(n,e){const i={};return e&&/^1\d{10}$/.test(e)&&(i.visitor_phone=e),t.post(`/h5/my/reservations/${n}/cancel`,i,{withAuth:!0})}export{r as a,n as b,a as c,v as d,w as e,e as f,h as g,i as h,s as i,o as j,u as k,g as l,f as m,d as n,c as o};

@ -0,0 +1 @@
import{h as t}from"./index-aMkUOzVg.js";function e(e={}){const{include_ticket_grab:i,...n}=e,s={...n};return i&&(s.include_ticket_grab=1),t.get("/h5/activities",s,{withAuth:!1})}function i(e){return t.get(`/h5/activities/${e}`,{})}function n(e){return t.post(`/h5/activities/${e}/view`,{},{withAuth:!1})}function s(e){return t.post(`/h5/activities/${e}/external-link-click`,{},{withAuth:!1})}function u(e){return t.get(`/h5/ticket-grab-events/${e}`,{},{withAuth:!1})}function r(e,i){return t.get(`/h5/ticket-grab-events/${e}/booking-info`,{venue_id:i},{withAuth:!0})}function h(e,i){return t.post(`/h5/ticket-grab-events/${e}/reservations`,i,{withAuth:!0})}function o(e={}){const i={};return e.only_included_in_stats&&(i.only_included_in_stats=1),t.get("/h5/venues",i,{withAuth:!1})}function c(){return t.get("/h5/venue-dicts",{},{withAuth:!1})}function a(e,i={}){const n={};return i.ticket_grab_event_id&&i.ticket_grab_event_id>0&&(n.ticket_grab_event_id=i.ticket_grab_event_id),t.get(`/h5/venues/${e}`,n,{withAuth:!1})}function v(e){return t.get(`/h5/study-tours/${e}`,{},{withAuth:!1})}function _(e={}){return t.get("/h5/study-tours",e,{withAuth:!1})}function g(e){return t.get(`/h5/activities/${e}/booking-info`,{},{withAuth:!0})}function f(e,i){return t.post(`/h5/activities/${e}/reservations`,i,{withAuth:!0})}function d(e){return t.get("/h5/my/reservations",{},{withAuth:!0})}function w(e,i){const n={};return i&&/^1\d{10}$/.test(i)&&(n.visitor_phone=i),t.get(`/h5/my/reservations/${e}`,n,{withAuth:!0})}function A(e,i){const n={};return i&&/^1\d{10}$/.test(i)&&(n.visitor_phone=i),t.post(`/h5/my/reservations/${e}/cancel`,n,{withAuth:!0})}export{o as a,e as b,_ as c,d,A as e,i as f,c as g,s as h,u as i,r as j,a as k,h as l,g as m,f as n,w as o,n as p,v as q};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
const A="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAaCAYAAACtv5zzAAABOElEQVRIidWVO0/DQBCEv3GMeIgeuUcR4Q9QI1yFgpbfR4eoKJIC8XDHq0iJxA9AogkVkgvg0JmESJZ9PmOuyEjX+HZnd7SePRljqMLG3clo9vmwMmABZ1zsSBw2EHvFaf226NSXrC3GsUGhyC2GMVJAfoiCstshG8IqiFn2ArVGm2P14bRTAZfR5j10KqCV+zO3hI5Y/iHH5n+c/FVn2uhHwZ/PFLQH2gF9VPF0cfIUSIEJsAv02ir4BB2AjkF56c52noImoD7okmJrVimQc989AdfAG3AOrJU67wMZkNQRuBT0QFegLdAF6Aj0Uuo8AyWuGSp6HDUZzarYB15nCnKfzn8V2CE3nIFBNwZtG5QblBqUGZR45PrsogID4Bl4BzY9cwq0eTJtYCvyokDoF83+o+Ng7DD6Br7JU2+T3aa8AAAAAElFTkSuQmCC",C="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAC7klEQVRIia2XbWiOYRTHf+fe82Dz9mEhFmqKqE3xyUv2YZLki8kHr0srRErUSPZF5G2USE2NNrViSygv+yA+0Hq8RVEjJY1pGZaVbc1cOtu1urs8e+77evKv032f6z7/61wv5zrnuiW3pY4QZlpJhtp+Ae+Ab8TDcqACKAL6gBRwAXgdZifsMxc4A2wCxqXp/jNwFjgV4fowUOW0LQC22MFcDTmWCcB1oDRDhwXASSAfODCCzQmgcoRvY4Er9nlJGwIDewyUGiCG7DewLI1dtYHKCH7SQK2BNaoHIJtB8JAKx3Y1yD4Pfj3I3AQikyL2zUVRSM8BDnnyNYb26h4PeBIDO3RdsalAsSdfsShh/ElhStIOxBejdI8Dzz2W0PsXkI+efJU36rTHk9QGYux7L0hNFo5rAoPcMQge0uDYnjPIAw/+EYPcCxA5jsjXoRWMlPuINDl2/YiUIfIwBv8iIlX6rkv9HmQFSCpieRpBNoL0p/nWBVIGcjsD/zTI9mFdkqnG4YDUvL0SmA+MCUVwN/AIaIkRuTk2bW61aVaPaitwHqgPG0oy1ZTFaYjEaGCyddyezjgxNPX/Di2HbZk6TRj5x/EMIC+k/wA6PEY2EZgSynB9tqz2jTRjLXfrgNm2PmP3+CfwEqgFGjI41Ng4CCyxyzyc0fqBT0AzcMwOAsl5ciOwNbI8xmx22duEC3V209brTHgLLAU69ThtAymPmXGqQYqdtjyQOpD8GPw59liJJpDdMZOHSi4iO522EkRmefSxAZHChEEK4sfNIBY6+mJPvuaLwmzqcTJUj3FupHExPptz7JbwLEq6nmN/z659NheBAZ3xH09SpzPLtCkxAh0a1Y89IlLllqM3e/LbEWkN7O2/J+ZIXwCXnbZWe1mPC81uXXoDeW6QMoN0RNwcXhlkvUG603zbYZD6CP5vgxw1SJ3qwjNNoYOYDqwF5mm4h34CvgPPgWv2By4TVgElwLTQf1kv8AG4CzwdbAH+Am0B5PBEueSbAAAAAElFTkSuQmCC";export{C as a,A as i};
const A="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAaCAYAAACtv5zzAAABOElEQVRIidWVO0/DQBCEv3GMeIgeuUcR4Q9QI1yFgpbfR4eoKJIC8XDHq0iJxA9AogkVkgvg0JmESJZ9PmOuyEjX+HZnd7SePRljqMLG3clo9vmwMmABZ1zsSBw2EHvFaf226NSXrC3GsUGhyC2GMVJAfoiCstshG8IqiFn2ArVGm2P14bRTAZfR5j10KqCV+zO3hI5Y/iHH5n+c/FVn2uhHwZ/PFLQH2gF9VPF0cfIUSIEJsAv02ir4BB2AjkF56c52noImoD7okmJrVimQc989AdfAG3AOrJU67wMZkNQRuBT0QFegLdAF6Aj0Uuo8AyWuGSp6HDUZzarYB15nCnKfzn8V2CE3nIFBNwZtG5QblBqUGZR45PrsogID4Bl4BzY9cwq0eTJtYCvyokDoF83+o+Ng7DD6Br7JU2+T3aa8AAAAAElFTkSuQmCC",C="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAC7klEQVRIia2XbWiOYRTHf+fe82Dz9mEhFmqKqE3xyUv2YZLki8kHr0srRErUSPZF5G2USE2NNrViSygv+yA+0Hq8RVEjJY1pGZaVbc1cOtu1urs8e+77evKv032f6z7/61wv5zrnuiW3pY4QZlpJhtp+Ae+Ab8TDcqACKAL6gBRwAXgdZifsMxc4A2wCxqXp/jNwFjgV4fowUOW0LQC22MFcDTmWCcB1oDRDhwXASSAfODCCzQmgcoRvY4Er9nlJGwIDewyUGiCG7DewLI1dtYHKCH7SQK2BNaoHIJtB8JAKx3Y1yD4Pfj3I3AQikyL2zUVRSM8BDnnyNYb26h4PeBIDO3RdsalAsSdfsShh/ElhStIOxBejdI8Dzz2W0PsXkI+efJU36rTHk9QGYux7L0hNFo5rAoPcMQge0uDYnjPIAw/+EYPcCxA5jsjXoRWMlPuINDl2/YiUIfIwBv8iIlX6rkv9HmQFSCpieRpBNoL0p/nWBVIGcjsD/zTI9mFdkqnG4YDUvL0SmA+MCUVwN/AIaIkRuTk2bW61aVaPaitwHqgPG0oy1ZTFaYjEaGCyddyezjgxNPX/Di2HbZk6TRj5x/EMIC+k/wA6PEY2EZgSynB9tqz2jTRjLXfrgNm2PmP3+CfwEqgFGjI41Ng4CCyxyzyc0fqBT0AzcMwOAsl5ciOwNbI8xmx22duEC3V209brTHgLLAU69ThtAymPmXGqQYqdtjyQOpD8GPw59liJJpDdMZOHSi4iO522EkRmefSxAZHChEEK4sfNIBY6+mJPvuaLwmzqcTJUj3FupHExPptz7JbwLEq6nmN/z659NheBAZ3xH09SpzPLtCkxAh0a1Y89IlLllqM3e/LbEWkN7O2/J+ZIXwCXnbZWe1mPC81uXXoDeW6QMoN0RNwcXhlkvUG603zbYZD6CP5vgxw1SJ3qwjNNoYOYDqwF5mm4h34CvgPPgWv2By4TVgElwLTQf1kv8AG4CzwdbAH+Am0B5PBEueSbAAAAAElFTkSuQmCC";export{A as a,C as i};

File diff suppressed because one or more lines are too long

@ -1 +1 @@
import{h as t,g as n,S as r,b as e}from"./index-ccvrD15-.js";function o(){return t.get("/h5/me/profile",{},{withAuth:!0})}function i(n){return t.put("/h5/me/profile",n,{withAuth:!0})}function u(){try{const t=n(r.localProfile);if(!t)return null;const e="string"==typeof t?JSON.parse(t):t;return e&&"object"==typeof e?e:null}catch{return null}}function l(t){e(r.localProfile,JSON.stringify(t))}export{o as g,u as l,l as s,i as u};
import{h as t,g as n,S as r,b as e}from"./index-aMkUOzVg.js";function o(){return t.get("/h5/me/profile",{},{withAuth:!0})}function i(n){return t.put("/h5/me/profile",n,{withAuth:!0})}function u(){try{const t=n(r.localProfile);if(!t)return null;const e="string"==typeof t?JSON.parse(t):t;return e&&"object"==typeof e?e:null}catch{return null}}function l(t){e(r.localProfile,JSON.stringify(t))}export{o as g,u as l,l as s,i as u};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
import{d as a,e as s,o as l,O as e,P as t,j as u,k as o,m as r,Q as c,n,p as d,y as i,z as m,I as f,E as p,F as _,G as v,t as y,x as g,C as h,J as k,K as b,_ as w}from"./index-ccvrD15-.js";import{c as j}from"./content.Dm7HR0OV.js";import{P as x}from"./poster.C6TYC1FC.js";import{s as C}from"./search.BJnl3YRK.js";const F=w(a({__name:"index",setup(a){const w=s([]),F=s(!1),P=s("");async function V(){F.value=!0;try{w.value=await j({keyword:P.value.trim()||void 0})||[]}catch{w.value=[]}finally{F.value=!1}}function B(){V()}return l(()=>{V()}),e(async()=>{try{await V()}finally{t()}}),(a,s)=>{const l=c("SubPageBackBtn"),e=i,t=f,j=r,V=y;return n(),u(j,{class:"page"},{default:o(()=>[d(l),d(j,{class:"toolbar toolbar--subpage-back"},{default:o(()=>[d(j,{class:"search-wrap"},{default:o(()=>[d(e,{class:"search-icon-img",src:m(C),mode:"aspectFit"},null,8,["src"]),d(t,{modelValue:P.value,"onUpdate:modelValue":s[0]||(s[0]=a=>P.value=a),class:"search-input",placeholder:"搜索研学路线名称","placeholder-class":"ph","confirm-type":"search",onConfirm:B},null,8,["modelValue"])]),_:1})]),_:1}),d(j,{class:"section"},{default:o(()=>[(n(!0),p(_,null,v(w.value,a=>(n(),u(j,{key:a.id,class:"tour-card",onClick:s=>{return l=a.id,void b({url:`/pages/study-tour/detail?id=${l}`});var l}},{default:o(()=>[d(j,{class:"tour-img-wrap"},{default:o(()=>[d(e,{class:"tour-img",src:a.cover_image||m(x),mode:"aspectFill"},null,8,["src"])]),_:2},1024),d(j,{class:"tour-body"},{default:o(()=>{var s;return[d(V,{class:"tour-title"},{default:o(()=>[g(h(a.name),1)]),_:2},1024),d(j,{class:"tour-stops-timeline"},{default:o(()=>[(n(!0),p(_,null,v(a.venue_names||[],(s,l)=>(n(),u(j,{key:"v-"+a.id+"-"+l,class:"tour-stop-row"},{default:o(()=>[d(j,{class:"tour-timeline-axis"},{default:o(()=>{var s;return[d(j,{class:"tour-dot-cell"},{default:o(()=>[d(j,{class:"tour-tl-dot"})]),_:1}),Number(l)<((null==(s=a.venue_names)?void 0:s.length)||0)-1?(n(),u(j,{key:0,class:"tour-tl-line"})):k("",!0)]}),_:2},1024),d(V,{class:"tour-stop-name"},{default:o(()=>[g(h(s),1)]),_:2},1024)]),_:2},1024))),128))]),_:2},1024),(null==(s=a.tags)?void 0:s.length)?(n(),u(j,{key:0,class:"tour-tags-row"},{default:o(()=>[(n(!0),p(_,null,v(a.tags,(s,l)=>(n(),u(V,{key:"t-"+a.id+"-"+l,class:"tour-tag"},{default:o(()=>[g(h(s),1)]),_:2},1024))),128))]),_:2},1024)):k("",!0)]}),_:2},1024)]),_:2},1032,["onClick"]))),128)),F.value&&!w.value.length?(n(),u(j,{key:0,class:"hint"},{default:o(()=>[g("加载中…")]),_:1})):k("",!0),F.value||w.value.length?k("",!0):(n(),u(j,{key:1,class:"hint muted"},{default:o(()=>[g("暂无研学路线")]),_:1}))]),_:1})]),_:1})}}}),[["__scopeId","data-v-d633b6a7"]]);export{F as default};
import{d as a,e as s,o as l,O as e,P as t,j as u,k as o,m as r,Q as c,n,p as d,y as i,z as m,I as f,E as p,F as _,G as v,t as y,x as g,C as h,J as k,K as b,_ as w}from"./index-aMkUOzVg.js";import{c as j}from"./content.O2lACI7T.js";import{P as x}from"./poster.C6TYC1FC.js";import{s as C}from"./search.BJnl3YRK.js";const F=w(a({__name:"index",setup(a){const w=s([]),F=s(!1),P=s("");async function V(){F.value=!0;try{w.value=await j({keyword:P.value.trim()||void 0})||[]}catch{w.value=[]}finally{F.value=!1}}function B(){V()}return l(()=>{V()}),e(async()=>{try{await V()}finally{t()}}),(a,s)=>{const l=c("SubPageBackBtn"),e=i,t=f,j=r,V=y;return n(),u(j,{class:"page"},{default:o(()=>[d(l),d(j,{class:"toolbar toolbar--subpage-back"},{default:o(()=>[d(j,{class:"search-wrap"},{default:o(()=>[d(e,{class:"search-icon-img",src:m(C),mode:"aspectFit"},null,8,["src"]),d(t,{modelValue:P.value,"onUpdate:modelValue":s[0]||(s[0]=a=>P.value=a),class:"search-input",placeholder:"搜索研学路线名称","placeholder-class":"ph","confirm-type":"search",onConfirm:B},null,8,["modelValue"])]),_:1})]),_:1}),d(j,{class:"section"},{default:o(()=>[(n(!0),p(_,null,v(w.value,a=>(n(),u(j,{key:a.id,class:"tour-card",onClick:s=>{return l=a.id,void b({url:`/pages/study-tour/detail?id=${l}`});var l}},{default:o(()=>[d(j,{class:"tour-img-wrap"},{default:o(()=>[d(e,{class:"tour-img",src:a.cover_image||m(x),mode:"aspectFill"},null,8,["src"])]),_:2},1024),d(j,{class:"tour-body"},{default:o(()=>{var s;return[d(V,{class:"tour-title"},{default:o(()=>[g(h(a.name),1)]),_:2},1024),d(j,{class:"tour-stops-timeline"},{default:o(()=>[(n(!0),p(_,null,v(a.venue_names||[],(s,l)=>(n(),u(j,{key:"v-"+a.id+"-"+l,class:"tour-stop-row"},{default:o(()=>[d(j,{class:"tour-timeline-axis"},{default:o(()=>{var s;return[d(j,{class:"tour-dot-cell"},{default:o(()=>[d(j,{class:"tour-tl-dot"})]),_:1}),Number(l)<((null==(s=a.venue_names)?void 0:s.length)||0)-1?(n(),u(j,{key:0,class:"tour-tl-line"})):k("",!0)]}),_:2},1024),d(V,{class:"tour-stop-name"},{default:o(()=>[g(h(s),1)]),_:2},1024)]),_:2},1024))),128))]),_:2},1024),(null==(s=a.tags)?void 0:s.length)?(n(),u(j,{key:0,class:"tour-tags-row"},{default:o(()=>[(n(!0),p(_,null,v(a.tags,(s,l)=>(n(),u(V,{key:"t-"+a.id+"-"+l,class:"tour-tag"},{default:o(()=>[g(h(s),1)]),_:2},1024))),128))]),_:2},1024)):k("",!0)]}),_:2},1024)]),_:2},1032,["onClick"]))),128)),F.value&&!w.value.length?(n(),u(j,{key:0,class:"hint"},{default:o(()=>[g("加载中…")]),_:1})):k("",!0),F.value||w.value.length?k("",!0):(n(),u(j,{key:1,class:"hint muted"},{default:o(()=>[g("暂无研学路线")]),_:1}))]),_:1})]),_:1})}}}),[["__scopeId","data-v-d633b6a7"]]);export{F as default};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save