From e7468ee9c110c1c4ff89749c971c273e3ac39ce0 Mon Sep 17 00:00:00 2001 From: wanghongjun <1445693971@qq.com> Date: Wed, 3 Jun 2026 16:01:16 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Admin/ParkingEquipmentController.php | 350 ++++++++++++++++++ app/Models/ParkingEquipment.php | 36 ++ app/Services/ParkingEquipmentService.php | 193 ++++++++++ app/common.php | 14 + ..._143449_create_parking_equipment_table.php | 37 ++ resources/lang/en/log.php | 5 + resources/lang/en/service.php | 8 + resources/lang/en/validation.php | 9 + resources/lang/zh-CN/log.php | 5 + resources/lang/zh-CN/service.php | 8 + resources/lang/zh-CN/validation.php | 9 + resources/lang/zh-TW/log.php | 5 + resources/lang/zh-TW/service.php | 8 + resources/lang/zh-TW/validation.php | 9 + routes/admin/api.php | 10 + 15 files changed, 706 insertions(+) create mode 100644 app/Http/Controllers/Admin/ParkingEquipmentController.php create mode 100644 app/Models/ParkingEquipment.php create mode 100644 app/Services/ParkingEquipmentService.php create mode 100644 database/migrations/2026_06_03_143449_create_parking_equipment_table.php diff --git a/app/Http/Controllers/Admin/ParkingEquipmentController.php b/app/Http/Controllers/Admin/ParkingEquipmentController.php new file mode 100644 index 0000000..41747f2 --- /dev/null +++ b/app/Http/Controllers/Admin/ParkingEquipmentController.php @@ -0,0 +1,350 @@ +service = $service; + } + + /** + * @param Request $request + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + try { + $query = ParkingEquipment::query(); + + if ($request->has('parking_id')) { + $parking_id = $request->input('parking_id'); + if ($parking_id) { + $channel_ids = ParkingChannel::getIds($parking_id); + if ($channel_ids) { + $query->whereIn('channel_id', $channel_ids); + } else { + $query->where('id', 0); + } + } + } + + if ($request->has('name')) { + $name = $request->input('name'); + if ($name) { + $query->where('name', 'like', "%{$name}%"); + } + } + + if ($request->has('type')) { + $type = $request->input('type'); + if ($type) { + $query->where('type', $type); + } + } + + if ($request->has('status')) { + $status = $request->input('status'); + if ($status) { + $query->where('status', $status); + } + } + + 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 = ParkingEquipmentService::getType(); + $statusArr = ParkingEquipmentService::getStatus(); + $purposeArr = ParkingEquipmentService::getPurpose(); + + $total = $query->count(); + $items = $query->latest()->forPage($page, $perPage)->get()->each( + function ($item) use ($typeArr, $statusArr, $purposeArr) { + $parking_id = ParkingChannel::getParkingId( + $item['channel_id'] + ); + $item['parking_name'] = Parking::getName($parking_id); + $item['name'] + = AdminTranslationService::getTranslationTypeName( + $item['id'], + 9, + $item['name'] + ); + $item['type_str'] = $typeArr[$item['type']]; + $item['purpose_str'] = $purposeArr[$item['purpose']] ?? ''; + $item['channel_name'] = ParkingChannel::getName( + $item['channel_id'] + ); + $item['status_str'] = $statusArr[$item['status']]; + 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 = [ + 'parking_list' => Parking::getData(), + 'type_list' => get_select_data( + ParkingEquipmentService::getType() + ), + 'status_list' => get_select_data( + ParkingEquipmentService::getStatus() + ) + ]; + 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( + ParkingEquipmentService::getType() + ), + 'purpose_list' => get_select_data( + ParkingEquipmentService::getPurpose() + ), + 'channel_list' => ParkingChannel::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( + __('admin.operation_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', + 'channel_id' => 'required', + 'purpose' => 'required' + ]; + $messages = [ + 'name.required' => __validation( + 'equipment_management.n_empty' + ), + 'type.required' => __validation( + 'equipment_management.t_empty' + ), + 'channel_id.required' => __validation( + 'equipment_management.ch_empty' + ), + 'purpose.required' => __validation( + 'equipment_management.pu_empty' + ) + ]; + if ($id) { + $this->validateId($id, ParkingEquipment::class); + } + if (isset($data['type'])) { + $type = $data['type']; + if (in_array($type, [2, 5])) { + $rules['ip'] = 'required|ip'; + $messages['ip.required'] + = __validation('equipment_management.ip_empty'); + $messages['ip.ip'] = __validation('equipment_management.ip'); + } + if (in_array($type, [3, 4, 5])) { + $rules['sn'] = 'required'; + $messages['sn.required'] + = __validation('equipment_management.sn_empty'); + } + } + $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, ParkingEquipment::class); + $data = [ + + 'type_list' => get_select_data( + ParkingEquipmentService::getType() + ), + 'purpose_list' => get_select_data( + ParkingEquipmentService::getPurpose() + ), + 'channel_list' => ParkingChannel::getData(), + 'item' => ParkingEquipment::query()->find($id) + ]; + $Translation = AdminTranslationService::getTranslation( + $data['item']['id'], + 9 + ); + $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( + __('admin.operation_failed') . ':' + . $e->getMessage() + ); + } + } + + /** + * @param string $id + * @return JsonResponse + * @throws CustomException + * @throws ValidationException + */ + public function destroy(string $id): JsonResponse + { + try { + $this->validateId($id, ParkingEquipment::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( + __('admin.operation_failed') . ':' + . $e->getMessage() + ); + } + } +} diff --git a/app/Models/ParkingEquipment.php b/app/Models/ParkingEquipment.php new file mode 100644 index 0000000..037273d --- /dev/null +++ b/app/Models/ParkingEquipment.php @@ -0,0 +1,36 @@ + 'type1', + 2 => 'type2', + 3 => 'type3', + 4 => 'type4', + 5 => 'type5' + ]; + + public static array $statusArr = ['offline', 'online']; + + public static array $purposeArr = [1 => 'entrance', 2 => 'export']; + + protected string $menuTitle = 'equipment_management'; + + /** + * @return array|string[] + */ + public static function getType(): array + { + $typeArr = self::$typeArr; + foreach ($typeArr as $key => $value) { + $typeArr[$key] = __('service.equipment_management.' . $value); + } + return $typeArr; + } + + /** + * @return array|string[] + */ + public static function getStatus(): array + { + $statusArr = self::$statusArr; + foreach ($statusArr as $key => $value) { + $statusArr[$key] = __('admin.' . $value); + } + return $statusArr; + } + + /** + * @return array|string[] + */ + public static function getPurpose(): array + { + $purposeArr = self::$purposeArr; + foreach ($purposeArr as $key => $value) { + $purposeArr[$key] = __('service.channel_management.' . $value); + } + return $purposeArr; + } + + /** + * @param array $data + * @throws Exception + */ + public function createModel(array $data) + { + try { + DB::beginTransaction(); + + $existsWhere = [ + ['name', '=', $data['name']], + ['type', '=', $data['type']] + ]; + if (ParkingEquipment::query()->where($existsWhere)->exists()) { + throw new Exception( + __service($this->menuTitle . '.name_exists') + ); + } + + $model = ParkingEquipment::query()->create([ + 'name' => $data['name'], + 'type' => $data['type'], + 'purpose' => $data['purpose'] ?? 0, + 'channel_id' => $data['channel_id'], + 'sn' => $data['sn'] ?? '', + 'ip' => $data['ip'] ?? '', + 'created_at' => get_datetime() + ]); + + $this->logService->logCreated($model, $this->menuTitle . '.create'); + + AdminTranslationService::saveTranslation( + $data['name'], + $data['en_name'] ?? '', + $data['tw_name'] ?? '', + $model->id, + 9 + ); + + 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']], + ['type', '=', $data['type']], + ['id', '<>', $id] + ]; + if (ParkingEquipment::query()->where($existsWhere)->exists()) { + throw new Exception( + __service($this->menuTitle . '.name_exists') + ); + } + + // 更新 + $model = ParkingEquipment::query()->findOrFail($id); + $oldValues = $model->toArray(); + + $model->update([ + 'name' => $data['name'], + 'type' => $data['type'], + 'purpose' => $data['purpose'] ?? 0, + 'channel_id' => $data['channel_id'], + 'sn' => $data['sn'] ?? '', + 'ip' => $data['ip'] ?? '', + 'updated_at' => get_datetime() + ]); + + $this->logService->logUpdated( + $model, + $oldValues, + $this->menuTitle . '.update' + ); + + AdminTranslationService::saveTranslation( + $data['name'], + $data['en_name'] ?? '', + $data['tw_name'] ?? '', + $id, + 9 + ); + + 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 = ParkingEquipment::query()->findOrFail($id); + + $this->logService->logDeleted($model, $this->menuTitle . '.delete'); + + $model->delete(); + + AdminTranslationService::syncDelete($id, 9); + + DB::commit(); + return true; + } catch (Exception $e) { + DB::rollBack(); + throw $e; + } + } +} diff --git a/app/common.php b/app/common.php index b54fefb..5230cb9 100644 --- a/app/common.php +++ b/app/common.php @@ -202,3 +202,17 @@ if (!function_exists('option_time')) { ]; } } + +if (!function_exists('__validation')) { + function __validation($str): string + { + return __('validation.' . $str); + } +} + +if (!function_exists('__service')) { + function __service($str): string + { + return __('service.' . $str); + } +} diff --git a/database/migrations/2026_06_03_143449_create_parking_equipment_table.php b/database/migrations/2026_06_03_143449_create_parking_equipment_table.php new file mode 100644 index 0000000..290a690 --- /dev/null +++ b/database/migrations/2026_06_03_143449_create_parking_equipment_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('name', 50)->default('')->comment('设备名称'); + $table->tinyInteger('type')->default(1)->comment('设备类型'); + $table->tinyInteger('purpose')->default(0)->comment('设备用途'); + $table->integer('channel_id')->comment('通道id'); + $table->string('sn', 78)->default('')->comment('设备编码'); + $table->string('ip', 15)->default('')->comment('设备ip'); + $table->tinyInteger('status')->default(1)->comment('状态'); + $table->timestamps(); + $table->softDeletes(); + $table->innoDb(); + $table->comment('停车场设备'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('parking_equipment'); + } +}; diff --git a/resources/lang/en/log.php b/resources/lang/en/log.php index d8c3f46..d148a9b 100644 --- a/resources/lang/en/log.php +++ b/resources/lang/en/log.php @@ -121,5 +121,10 @@ return [ 'create' => 'Create channel permissions', 'update' => 'Update channel permissions', 'delete' => 'Delete channel permissions' + ], + 'equipment_management' => [ + 'create' => 'Create device', + 'update' => 'Update device', + 'delete' => 'Delete device' ] ]; diff --git a/resources/lang/en/service.php b/resources/lang/en/service.php index a386b6a..3a9a266 100644 --- a/resources/lang/en/service.php +++ b/resources/lang/en/service.php @@ -113,5 +113,13 @@ return [ ], 'channel_permissions' => [ 'type_exists' => 'Channel membership type already exists' + ], + 'equipment_management' => [ + 'type1' => 'barrier gate', + 'type2' => 'Entrance channel camera', + 'type3' => 'traffic light', + 'type4' => '32 inch display screen', + 'type5' => 'Koto recognition camera', + 'name_exists' => 'The same type of device name already exists' ] ]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 61983be..d772330 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -154,5 +154,14 @@ return [ 'm_empty' => 'Member type cannot be empty', 'c_empty' => 'Allow channel not to be empty', 'c_array' => 'Allow channel data to be an array' + ], + 'equipment_management' => [ + 'n_empty' => 'The device name cannot be empty', + 't_empty' => 'The device type cannot be empty', + 'ch_empty' => 'Binding channel cannot be empty', + 'pu_empty' => 'The purpose of the device cannot be empty', + 'ip_empty' => 'The device IP cannot be empty', + 'ip' => 'Device IP format error', + 'sn_empty' => 'The device code cannot be empty' ] ]; diff --git a/resources/lang/zh-CN/log.php b/resources/lang/zh-CN/log.php index 9534d00..712eb94 100644 --- a/resources/lang/zh-CN/log.php +++ b/resources/lang/zh-CN/log.php @@ -121,5 +121,10 @@ return [ 'create' => '创建通道权限', 'update' => '更新通道权限', 'delete' => '删除通道权限' + ], + 'equipment_management' => [ + 'create' => '创建设备', + 'update' => '更新设备', + 'delete' => '删除设备' ] ]; diff --git a/resources/lang/zh-CN/service.php b/resources/lang/zh-CN/service.php index 61686a1..b05753f 100644 --- a/resources/lang/zh-CN/service.php +++ b/resources/lang/zh-CN/service.php @@ -113,5 +113,13 @@ return [ ], 'channel_permissions' => [ 'type_exists' => '通道会员类型已存在' + ], + 'equipment_management' => [ + 'type1' => '道闸', + 'type2' => '入口通道相机', + 'type3' => '交通灯', + 'type4' => '32寸显示屏', + 'type5' => '科拓识别相机', + 'name_exists' => '同类型设备名称已存在' ] ]; diff --git a/resources/lang/zh-CN/validation.php b/resources/lang/zh-CN/validation.php index 4768766..1822409 100644 --- a/resources/lang/zh-CN/validation.php +++ b/resources/lang/zh-CN/validation.php @@ -154,5 +154,14 @@ return [ 'm_empty' => '会员类型不能为空', 'c_empty' => '允许通道不能为空', 'c_array' => '允许通道数据必须是数组' + ], + 'equipment_management' => [ + 'n_empty' => '设备名称不能为空', + 't_empty' => '设备类型不能为空', + 'ch_empty' => '绑定通道不能为空', + 'pu_empty' => '设备用途不能为空', + 'ip_empty' => '设备IP不能为空', + 'ip' => '设备IP格式错误', + 'sn_empty' => '设备编码不能为空' ] ]; diff --git a/resources/lang/zh-TW/log.php b/resources/lang/zh-TW/log.php index 2a044ae..8e64aa1 100644 --- a/resources/lang/zh-TW/log.php +++ b/resources/lang/zh-TW/log.php @@ -121,5 +121,10 @@ return [ 'create' => '創建通道許可權', 'update' => '更新通道許可權', 'delete' => '删除通道許可權' + ], + 'equipment_management' => [ + 'create' => '創建設備', + 'update' => '更新設備', + 'delete' => '删除設備' ] ]; diff --git a/resources/lang/zh-TW/service.php b/resources/lang/zh-TW/service.php index 9362a38..47d8e98 100644 --- a/resources/lang/zh-TW/service.php +++ b/resources/lang/zh-TW/service.php @@ -113,5 +113,13 @@ return [ ], 'channel_permissions' => [ 'type_exists' => '通道會員類型已存在' + ], + 'equipment_management' => [ + 'type1' => '道閘', + 'type2' => '入口通道相機', + 'type3' => '交通燈', + 'type4' => '32寸顯示幕', + 'type5' => '科拓識別相機', + 'name_exists' => '同類型設備名稱已存在' ] ]; diff --git a/resources/lang/zh-TW/validation.php b/resources/lang/zh-TW/validation.php index ba6d07b..332bd0f 100644 --- a/resources/lang/zh-TW/validation.php +++ b/resources/lang/zh-TW/validation.php @@ -154,5 +154,14 @@ return [ 'm_empty' => '會員類型不能為空', 'c_empty' => '允許通道不能為空', 'c_array' => '允許通道數據必須是數組' + ], + 'equipment_management' => [ + 'n_empty' => '設備名稱不能為空', + 't_empty' => '設備類型不能為空', + 'ch_empty' => '綁定通道不能為空', + 'pu_empty' => '設備用途不能為空', + 'ip_empty' => '設備IP不能為空', + 'ip' => '設備IP格式錯誤', + 'sn_empty' => '設備編碼不能為空' ] ]; diff --git a/routes/admin/api.php b/routes/admin/api.php index 72a4e26..057d80a 100644 --- a/routes/admin/api.php +++ b/routes/admin/api.php @@ -35,6 +35,7 @@ use App\Http\Controllers\Admin\ParkingChannelController; use App\Http\Controllers\Admin\GuardBoothManagementController; use App\Http\Controllers\Admin\ParkingDepartureReasonController; use App\Http\Controllers\Admin\ChannelPermissionsController; +use App\Http\Controllers\Admin\ParkingEquipmentController; Route::group(['prefix' => 'admin'], function () { @@ -251,6 +252,15 @@ Route::group(['prefix' => 'admin'], function () { Route::delete('/channelManagement/{id}', [ParkingChannelController::class, 'destroy']); Route::get('/channelManagement/rule', [ParkingChannelController::class, 'rule']); Route::get('/channelManagement/search', [ParkingChannelController::class, 'search']); + // 设备管理 + Route::get('/equipmentManagement', [ParkingEquipmentController::class, 'index']); + Route::get('/equipmentManagement/create', [ParkingEquipmentController::class, 'create']); + Route::post('/equipmentManagement', [ParkingEquipmentController::class, 'store']); + Route::get('/equipmentManagement/edit/{id}', [ParkingEquipmentController::class, 'edit']); + Route::put('/equipmentManagement/{id}', [ParkingEquipmentController::class, 'update']); + Route::delete('/equipmentManagement/{id}', [ParkingEquipmentController::class, 'destroy']); + Route::get('/equipmentManagement/rule', [ParkingEquipmentController::class, 'rule']); + Route::get('/equipmentManagement/search', [ParkingEquipmentController::class, 'search']); // 岗亭管理 Route::get('/guardBoothManagement', [GuardBoothManagementController::class, 'index']); Route::post('/guardBoothManagement', [GuardBoothManagementController::class, 'store']);