diff --git a/app/Http/Controllers/Admin/ParkingChannelController.php b/app/Http/Controllers/Admin/ParkingChannelController.php new file mode 100644 index 0000000..a8c312e --- /dev/null +++ b/app/Http/Controllers/Admin/ParkingChannelController.php @@ -0,0 +1,308 @@ +service = $service; + } + + /** + * @param Request $request + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + try { + $query = ParkingChannel::query(); + + if ($request->has('type')) { + $type = $request->input('type'); + if ($type) { + $query->where('type', $type); + } + } + + if ($request->has('name')) { + $name = $request->input('name'); + if ($name) { + $query->where('name', 'like', "%{$name}%"); + } + } + + if ($request->has('create_start_time') + && $request->has( + 'create_end_time' + ) + ) { + $create_start_time = $request->input('create_start_time'); + $create_end_time = $request->input('create_end_time'); + if ($create_start_time && $create_end_time) { + $query->whereBetween('created_at', [ + $create_start_time . ' 00:00:00', + $create_end_time . ' 23:59:59' + ]); + } + } + + // 分页 + $page = $request->input('page', 1); + $perPage = $request->input('per_page', 10); + $typeArr = ParkingChannelService::getType(); + $positionArr = ParkingChannelService::getPosition(); + $total = $query->count(); + $items = $query->latest()->forPage($page, $perPage)->get()->each( + function ($item) use ($typeArr, $positionArr) { + $item['name'] + = AdminTranslationService::getTranslationTypeName( + $item['id'], + 6, + $item['name'] + ); + $item['type_str'] = $typeArr[$item['type']]; + $item['position_str'] = $positionArr[$item['position']]; + $item['guard_booth'] = ParkingGuardBooth::getName($item['guard_booth_id']); + $item['parking'] = Parking::getName($item['parking_id']); + return $item; + } + ); + + return $this->responseService->success([ + 'items' => $items, + 'total' => $total, + 'page' => $page, + 'per_page' => $perPage, + 'last_page' => ceil($total / $perPage), + ]); + } catch (Exception $e) { + $m_prefix = __('exception.exception_handler.resource'); + return $this->responseService->systemError( + $m_prefix . ':' . $e->getMessage() + ); + } + } + + /** + * 列表搜索数据 + * @return JsonResponse + */ + public function search(): JsonResponse + { + try { + $data = [ + 'type_list' => get_select_data(ParkingChannelService::getType()) + ]; + return $this->responseService->success($data); + } catch (Exception $e) { + $m_prefix = __('exception.exception_handler.resource'); + return $this->responseService->systemError( + $m_prefix . ':' . $e->getMessage() + ); + } + } + + /** + * @return JsonResponse + */ + public function create(): JsonResponse + { + try { + $data = [ + 'type_list' => get_select_data( + ParkingChannelService::getType() + ), + 'position_list' => get_select_data( + ParkingChannelService::getPosition() + ), + 'parking_list' => Parking::getData(), + 'guard_booth_list' => ParkingGuardBooth::getData() + ]; + return $this->responseService->success($data); + } catch (Exception $e) { + return $this->responseService->systemError( + __('exception.get_data_failed') . ':' . $e->getMessage() + ); + } + } + + /** + * @param Request $request + * @return JsonResponse + * @throws CustomException + * @throws ValidationException + */ + public function store(Request $request): JsonResponse + { + try { + $this->saveValidator($request->all()); + $this->service->createModel($request->all()); + return $this->responseService->success( + null, + __('admin.save_succeeded') + ); + } catch (ValidationException|CustomException $e) { + throw $e; + } catch (Exception $e) { + return $this->responseService->systemError( + __('exception.admin_floor.create_failed') . ':' + . $e->getMessage() + ); + } + } + + /** + * @param array $data + * @param int $id + * @return void + * @throws ValidationException + */ + protected function saveValidator(array $data, int $id = 0): void + { + $rules = [ + 'name' => 'required', + 'type' => 'required', + 'position' => 'required', + 'parking_id' => 'required', + 'guard_booth_id' => 'required' + ]; + $messages = [ + 'name.required' => __( + 'validation.channel_management.n_empty' + ), + 'type.required' => __( + 'validation.channel_management.t_empty' + ), + 'position.required' => __( + 'validation.channel_management.po_empty' + ), + 'parking_id.required' => __( + 'validation.channel_management.pa_empty' + ), + 'guard_booth_id.required' => __( + 'validation.channel_management.g_empty' + ) + ]; + if ($id) { + $this->validateId($id, ParkingChannel::class); + } + $validator = Validator::make($data, $rules, $messages); + + if ($validator->fails()) { + throw new ValidationException($validator); + } + } + + /** + * @param string $id + * @return JsonResponse + */ + public function edit(string $id): JsonResponse + { + try { + $this->validateId($id, ParkingChannel::class); + $data = [ + 'type_list' => get_select_data( + ParkingChannelService::$typeArr + ), + 'position_list' => get_select_data( + ParkingChannelService::$positionArr + ), + 'parking_list' => Parking::getData(), + 'guard_booth_list' => ParkingGuardBooth::getData(), + 'item' => ParkingChannel::query()->find($id) + ]; + $Translation = AdminTranslationService::getTranslation( + $data['item']['id'], + 6 + ); + $data['item']['en_name'] = $Translation['en'] ?? ''; + $data['item']['tw_name'] = $Translation['zh_tw'] ?? ''; + return $this->responseService->success($data); + } catch (Exception $e) { + return $this->responseService->systemError( + __('exception.get_data_failed') . ':' . $e->getMessage() + ); + } + } + + /** + * @param Request $request + * @param string $id + * @return JsonResponse + * @throws CustomException + * @throws ValidationException + */ + public function update(Request $request, string $id): JsonResponse + { + try { + $this->saveValidator($request->all(), $id); + $this->service->updateModel($request->all(), $id); + return $this->responseService->success( + null, + __('admin.update_succeeded') + ); + } catch (ValidationException|CustomException $e) { + throw $e; + } catch (Exception $e) { + return $this->responseService->systemError( + __('exception.admin_floor.update_failed') . ':' + . $e->getMessage() + ); + } + } + + /** + * @param string $id + * @return JsonResponse + * @throws CustomException + * @throws ValidationException + */ + public function destroy(string $id): JsonResponse + { + try { + $this->validateId($id, ParkingChannel::class); + $this->service->deleteModel($id); + return $this->responseService->success( + null, + __('admin.delete_succeeded') + ); + } catch (ValidationException|CustomException $e) { + throw $e; + } catch (Exception $e) { + return $this->responseService->systemError( + __('exception.admin_floor.destroy_failed') . ':' + . $e->getMessage() + ); + } + } +} diff --git a/app/Models/ParkingChannel.php b/app/Models/ParkingChannel.php new file mode 100644 index 0000000..fde298f --- /dev/null +++ b/app/Models/ParkingChannel.php @@ -0,0 +1,33 @@ +select(['id', 'name'])->get()->each( + function ($item) { + $item['name'] = AdminTranslationService::getTranslationTypeName( + $item['id'], + 7, + $item['name'] + ); + return $item; + } + )->toArray(); + } + + public function getCreatedAtAttribute($value): string + { + return $value ? date("Y-m-d H:i:s", strtotime($value)) : $value; + } + + public static function getName($id) + { + $name = self::query()->value('name'); + return AdminTranslationService::getTranslationTypeName($id, 7, $name); + } +} diff --git a/app/Services/ParkingChannelService.php b/app/Services/ParkingChannelService.php new file mode 100644 index 0000000..9e7fe38 --- /dev/null +++ b/app/Services/ParkingChannelService.php @@ -0,0 +1,176 @@ + 'entrance', + 2 => 'export' + ]; + + public static array $positionArr = [ + 1 => 'on-site', + 2 => 'off-site' + ]; + + /** + * @return array|string[] + */ + public static function getType(): array + { + $typeArr = self::$typeArr; + foreach ($typeArr as $key => $value) { + $typeArr[$key] = __('service.channel_management.' . $value); + } + return $typeArr; + } + + /** + * @return array|string[] + */ + public static function getPosition(): array + { + $positionArr = self::$positionArr; + foreach ($positionArr as $key => $value) { + $positionArr[$key] = __('service.channel_management.' . $value); + } + return $positionArr; + } + + /** + * @param array $data + * @throws Exception + */ + public function createModel(array $data) + { + try { + DB::beginTransaction(); + + $existsWhere = [ + ['name', '=', $data['name']], + ['parking_id', '=', $data['parking_id']], + ['guard_booth_id', '=', $data['guard_booth_id']] + ]; + if (ParkingChannel::query()->where($existsWhere)->exists()) { + throw new Exception(__('service.channel_management.name_exists')); + } + + $model = ParkingChannel::query()->create([ + 'name' => $data['name'], + 'type' => $data['type'], + 'position' => $data['position'], + 'parking_id' => $data['parking_id'], + 'guard_booth_id' => $data['guard_booth_id'], + 'third_party_channel' => $data['third_party_channel'] ?? '', + 'created_at' => get_datetime() + ]); + + $this->logService->logCreated($model, 'channel_management.create'); + + AdminTranslationService::saveTranslation( + $data['name'], + $data['en_name'] ?? '', + $data['tw_name'] ?? '', + $model->id, + 6 + ); + + DB::commit(); + return $model; + } catch (Exception $e) { + DB::rollBack(); + throw $e; + } + } + + /** + * @param array $data + * @param int $id + * @throws Exception + */ + public function updateModel(array $data, int $id) + { + try { + DB::beginTransaction(); + + // 验证 + $existsWhere = [ + ['name', '=', $data['name']], + ['parking_id', '=', $data['parking_id']], + ['guard_booth_id', '=', $data['guard_booth_id']], + ['id', '<>', $id] + ]; + if (ParkingChannel::query()->where($existsWhere)->exists()) { + throw new Exception(__('service.channel_management.name_exists')); + } + + // 更新 + $model = ParkingChannel::query()->findOrFail($id); + $oldValues = $model->toArray(); + + $model->update([ + 'name' => $data['name'], + 'type' => $data['type'], + 'position' => $data['position'], + 'parking_id' => $data['parking_id'], + 'guard_booth_id' => $data['guard_booth_id'], + 'third_party_channel' => $data['third_party_channel'] ?? '', + 'updated_at' => get_datetime() + ]); + + $this->logService->logUpdated( + $model, + $oldValues, + 'channel_management.update' + ); + + AdminTranslationService::saveTranslation( + $data['name'], + $data['en_name'] ?? '', + $data['tw_name'] ?? '', + $id, + 6 + ); + + DB::commit(); + return $model; + } catch (Exception $e) { + DB::rollBack(); + throw $e; + } + } + + /** + * @param $id + * @return bool + * @throws Exception + */ + public function deleteModel($id): bool + { + try { + DB::beginTransaction(); + + $model = ParkingChannel::query()->findOrFail($id); + + $this->logService->logDeleted($model, 'channel_management.delete'); + + $model->delete(); + + AdminTranslationService::syncDelete($id, 6); + + DB::commit(); + return true; + } catch (Exception $e) { + DB::rollBack(); + throw $e; + } + } +} diff --git a/database/migrations/2026_06_02_143449_create_parking_channel_table.php b/database/migrations/2026_06_02_143449_create_parking_channel_table.php new file mode 100644 index 0000000..f6dcc68 --- /dev/null +++ b/database/migrations/2026_06_02_143449_create_parking_channel_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('name', 50)->comment('通道名称'); + $table->tinyInteger('type')->default(1)->comment('通道类型'); + $table->tinyInteger('position')->default(1)->comment('通道位置'); + $table->integer('parking_id')->comment('所属停车场'); + $table->integer('guard_booth_id')->comment('所属岗亭'); + $table->string('third_party_channel', 255)->default('')->comment('第三方通道'); + $table->timestamps(); + $table->softDeletes(); + $table->innoDb(); + $table->comment('停车场通道'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('parking_channel'); + } +}; diff --git a/database/migrations/2026_06_02_143449_create_parking_guard_booth_table.php b/database/migrations/2026_06_02_143449_create_parking_guard_booth_table.php new file mode 100644 index 0000000..5d9e687 --- /dev/null +++ b/database/migrations/2026_06_02_143449_create_parking_guard_booth_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('name', 50)->comment('岗亭名称'); + $table->string('remake', 255)->default('')->comment('备注'); + $table->timestamps(); + $table->softDeletes(); + $table->innoDb(); + $table->comment('停车场通道'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('parking_guard_booth'); + } +}; diff --git a/resources/lang/en/log.php b/resources/lang/en/log.php index e6dfb25..e1bd644 100644 --- a/resources/lang/en/log.php +++ b/resources/lang/en/log.php @@ -101,5 +101,10 @@ return [ 'create' => 'Create a parking lot administrator', 'update' => 'Update parking lot administrator', 'delete' => 'Delete parking lot administrator' + ], + 'channel_management' => [ + 'create' => 'Create channel', + 'update' => 'Update channel', + 'delete' => 'Delete channel' ] ]; diff --git a/resources/lang/zh-CN/log.php b/resources/lang/zh-CN/log.php index a5160e5..a71cf69 100644 --- a/resources/lang/zh-CN/log.php +++ b/resources/lang/zh-CN/log.php @@ -101,5 +101,10 @@ return [ 'create' => '创建停车场管理员', 'update' => '更新停车场管理员', 'delete' => '删除停车场管理员' + ], + 'channel_management' => [ + 'create' => '创建通道', + 'update' => '更新通道', + 'delete' => '删除通道' ] ]; diff --git a/resources/lang/zh-TW/log.php b/resources/lang/zh-TW/log.php index a697c8b..8c8b8d3 100644 --- a/resources/lang/zh-TW/log.php +++ b/resources/lang/zh-TW/log.php @@ -101,5 +101,10 @@ return [ 'create' => '創建停車場管理員', 'update' => '更新停車場管理員', 'delete' => '删除停車場管理員' + ], + 'channel_management' => [ + 'create' => '創建通道', + 'update' => '更新通道', + 'delete' => '删除通道' ] ]; diff --git a/routes/admin/api.php b/routes/admin/api.php index 5c08b6d..4f6fa41 100644 --- a/routes/admin/api.php +++ b/routes/admin/api.php @@ -31,6 +31,7 @@ use App\Http\Controllers\Admin\ParkingManagementController; use App\Http\Controllers\Admin\ParkingManagementListController; use App\Http\Controllers\Admin\ParkingAttendantController; use App\Http\Controllers\Admin\RegionalManagementController; +use App\Http\Controllers\Admin\ParkingChannelController; Route::group(['prefix' => 'admin'], function () { @@ -238,6 +239,15 @@ Route::group(['prefix' => 'admin'], function () { Route::delete('/regionalManagement/{id}', [RegionalManagementController::class, 'destroy']); Route::get('/regionalManagement/rule', [RegionalManagementController::class, 'rule']); Route::get('/regionalManagement/search', [RegionalManagementController::class, 'search']); + // 通道管理 + Route::get('/channelManagement', [ParkingChannelController::class, 'index']); + Route::get('/channelManagement/create', [ParkingChannelController::class, 'create']); + Route::post('/channelManagement', [ParkingChannelController::class, 'store']); + Route::get('/channelManagement/edit/{id}', [ParkingChannelController::class, 'edit']); + Route::put('/channelManagement/{id}', [ParkingChannelController::class, 'update']); + Route::delete('/channelManagement/{id}', [ParkingChannelController::class, 'destroy']); + Route::get('/channelManagement/rule', [ParkingChannelController::class, 'rule']); + Route::get('/channelManagement/search', [ParkingChannelController::class, 'search']); // 角色 Route::get('/roles', [RolesController::class, 'index']); Route::get('/roles/create', [RolesController::class, 'create']);