You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
9.9 KiB

1 week ago
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
3 days ago
use App\Models\StudyTour;
1 week ago
use App\Models\Venue;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
3 days ago
use Illuminate\Support\Facades\DB;
1 week ago
use Illuminate\Validation\Rule;
1 week ago
class VenueController extends Controller
{
public function store(Request $request): JsonResponse
{
1 week ago
$user = $request->user();
abort_unless($user && ($user->isSuperAdmin() || $user->role === 'venue_admin'), 403, '无权限');
1 week ago
$data = $request->validate([
'name' => ['required', 'string', 'max:120'],
'venue_type' => ['nullable', 'string', 'max:80'],
1 week ago
'venue_types' => ['nullable', 'array'],
'venue_types.*' => ['string', 'max:80'],
1 week ago
'unit_name' => ['nullable', 'string', 'max:120'],
'district' => ['nullable', 'string', 'max:80'],
'ticket_type' => ['nullable', 'string', 'max:80'],
1 week ago
'appointment_type' => ['nullable', 'string', 'max:40'],
1 week ago
'open_mode' => ['nullable', 'string', 'max:40', Rule::in(['fulltime', 'scheduled', 'appointment'])],
3 days ago
'open_time' => ['nullable', 'string', 'max:65535'],
1 week ago
'reservation_notice' => ['nullable', 'string'],
1 week ago
'ticket_content' => ['nullable', 'string'],
'booking_method' => ['nullable', 'string'],
'visit_form' => ['nullable', 'string'],
'consultation_hours' => ['nullable', 'string'],
1 week ago
'address' => ['nullable', 'string', 'max:255'],
3 days ago
'contact_phone' => ['nullable', 'string', 'max:255'],
1 week ago
'lat' => ['nullable', 'numeric'],
'lng' => ['nullable', 'numeric'],
'cover_image' => ['nullable', 'string', 'max:255'],
'gallery_media' => ['nullable', 'array'],
'gallery_media.*.type' => ['required_with:gallery_media', 'in:image,video'],
'gallery_media.*.url' => ['required_with:gallery_media', 'string', 'max:255'],
'detail_html' => ['nullable', 'string'],
'live_people_count' => ['nullable', 'integer', 'min:0'],
'sort' => ['nullable', 'integer', 'min:0'],
'is_active' => ['boolean'],
]);
1 week ago
$auditStatus = $user->isSuperAdmin() ? Venue::AUDIT_APPROVED : Venue::AUDIT_PENDING;
$venue = Venue::create($data + [
'is_active' => $data['is_active'] ?? true,
'audit_status' => $auditStatus,
'audit_remark' => null,
'last_approved_snapshot' => null,
]);
if (! $user->isSuperAdmin()) {
$user->venues()->syncWithoutDetaching([$venue->id]);
}
return response()->json($venue->fresh(), 201);
1 week ago
}
public function index(Request $request): JsonResponse
{
$user = $request->user();
$keyword = trim((string) $request->string('keyword'));
$district = trim((string) $request->string('district'));
$venueType = trim((string) $request->string('venue_type'));
$ticketType = trim((string) $request->string('ticket_type'));
1 week ago
$openMode = trim((string) $request->string('open_mode'));
1 week ago
$isActive = $request->input('is_active');
1 week ago
$auditStatus = trim((string) $request->string('audit_status'));
1 week ago
if ($user->isSuperAdmin()) {
$query = Venue::query();
} else {
$query = $user->venues();
}
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 . '%')
1 week ago
->orWhere('ticket_content', 'like', '%' . $keyword . '%')
->orWhere('booking_method', 'like', '%' . $keyword . '%')
->orWhere('visit_form', 'like', '%' . $keyword . '%')
->orWhere('consultation_hours', 'like', '%' . $keyword . '%');
1 week ago
});
}
if ($district !== '') {
$query->where('district', $district);
}
if ($venueType !== '') {
1 week ago
$query->where(function ($q) use ($venueType) {
$q->where('venue_type', $venueType)
->orWhereJsonContains('venue_types', $venueType);
});
1 week ago
}
if ($ticketType !== '') {
$query->where('ticket_type', $ticketType);
}
1 week ago
if ($openMode !== '') {
$query->where('open_mode', $openMode);
}
1 week ago
if ($isActive !== null && $isActive !== '') {
$query->where('is_active', (int) $isActive === 1);
}
1 week ago
if ($auditStatus !== '') {
$query->where('audit_status', $auditStatus);
}
1 week ago
$venues = $query->orderBy('venues.sort')->orderByDesc('venues.id')->get();
return response()->json($venues);
}
public function update(Request $request, int $id): JsonResponse
{
$venue = Venue::query()->findOrFail($id);
$this->ensureVenuePermission($request, $venue->id);
1 week ago
$user = $request->user();
1 week ago
$data = $request->validate([
'name' => ['sometimes', 'string', 'max:120'],
'venue_type' => ['nullable', 'string', 'max:80'],
1 week ago
'venue_types' => ['nullable', 'array'],
'venue_types.*' => ['string', 'max:80'],
1 week ago
'unit_name' => ['nullable', 'string', 'max:120'],
'district' => ['nullable', 'string', 'max:80'],
'ticket_type' => ['nullable', 'string', 'max:80'],
1 week ago
'appointment_type' => ['nullable', 'string', 'max:40'],
1 week ago
'open_mode' => ['nullable', 'string', 'max:40', Rule::in(['fulltime', 'scheduled', 'appointment'])],
3 days ago
'open_time' => ['nullable', 'string', 'max:65535'],
1 week ago
'reservation_notice' => ['nullable', 'string'],
1 week ago
'ticket_content' => ['nullable', 'string'],
'booking_method' => ['nullable', 'string'],
'visit_form' => ['nullable', 'string'],
'consultation_hours' => ['nullable', 'string'],
1 week ago
'address' => ['nullable', 'string', 'max:255'],
3 days ago
'contact_phone' => ['nullable', 'string', 'max:255'],
1 week ago
'lat' => ['nullable', 'numeric'],
'lng' => ['nullable', 'numeric'],
'cover_image' => ['nullable', 'string', 'max:255'],
'gallery_media' => ['nullable', 'array'],
'gallery_media.*.type' => ['required_with:gallery_media', 'in:image,video'],
'gallery_media.*.url' => ['required_with:gallery_media', 'string', 'max:255'],
'detail_html' => ['nullable', 'string'],
'live_people_count' => ['nullable', 'integer', 'min:0'],
'sort' => ['nullable', 'integer', 'min:0'],
'is_active' => ['boolean'],
]);
1 week ago
if (! $user->isSuperAdmin()) {
1 week ago
unset($data['sort']);
1 week ago
if ($venue->audit_status === Venue::AUDIT_APPROVED) {
$venue->last_approved_snapshot = $venue->buildAuditSnapshot();
}
$data['audit_status'] = Venue::AUDIT_PENDING;
$data['audit_remark'] = null;
} else {
$data['audit_status'] = Venue::AUDIT_APPROVED;
$data['audit_remark'] = null;
$data['last_approved_snapshot'] = null;
1 week ago
}
$venue->fill($data)->save();
1 week ago
return response()->json($venue->fresh());
}
public function approve(Request $request, int $id): JsonResponse
{
abort_unless($request->user()?->isSuperAdmin(), 403, '仅超级管理员可审核');
$venue = Venue::query()->findOrFail($id);
$venue->audit_status = Venue::AUDIT_APPROVED;
$venue->audit_remark = null;
$venue->last_approved_snapshot = null;
$venue->save();
return response()->json($venue->fresh());
}
public function reject(Request $request, int $id): JsonResponse
{
abort_unless($request->user()?->isSuperAdmin(), 403, '仅超级管理员可审核');
$data = $request->validate([
'remark' => ['nullable', 'string', 'max:2000'],
]);
$venue = Venue::query()->findOrFail($id);
$venue->audit_status = Venue::AUDIT_REJECTED;
$venue->audit_remark = $data['remark'] ?? null;
$venue->save();
return response()->json($venue->fresh());
1 week ago
}
3 days ago
/**
* 仅超级管理员。数据库外键会级联删除该场馆下的活动、预约关联等;并从研学线路的 venue_ids 中移除该馆。
*/
public function destroy(Request $request, int $id): JsonResponse
{
abort_unless($request->user()?->isSuperAdmin(), 403, '仅超级管理员可删除场馆');
$venue = Venue::query()->findOrFail($id);
DB::transaction(function () use ($venue) {
$vid = (int) $venue->id;
StudyTour::query()->orderBy('id')->chunk(50, function ($tours) use ($vid) {
foreach ($tours as $tour) {
$ids = $tour->venue_ids;
if (! is_array($ids) || $ids === []) {
continue;
}
$new = array_values(array_filter($ids, fn ($x) => (int) $x !== $vid));
if (count($new) !== count($ids)) {
$tour->venue_ids = $new;
$tour->save();
}
}
});
$venue->delete();
});
return response()->json(['message' => '删除成功']);
}
1 week ago
private function ensureVenuePermission(Request $request, int $venueId): void
{
$user = $request->user();
if ($user->isSuperAdmin()) {
return;
}
$allowed = $user->venues()->where('venues.id', $venueId)->exists();
abort_unless($allowed, 403, '仅可操作已绑定场馆');
}
}