diff --git a/app/Exports/AdminNoticeExport.php b/app/Exports/AdminNoticeExport.php new file mode 100644 index 0000000..e22c21d --- /dev/null +++ b/app/Exports/AdminNoticeExport.php @@ -0,0 +1,62 @@ +each( + function ($item) use (&$data, &$index) { + $oldItem = (new AdminNoticeService( + new OperationLogService() + ))->getItem($item); + $data[] = [ + 'id' => $index, + 'alarm_time' => $oldItem['alarm_time'], + 'alarm_type' => $oldItem['alarm_type'], + 'msg_type' => $oldItem['msg_type'], + 'space_number' => $oldItem['parking_space_number'], + 'license_plate' => $oldItem['license_plate'], + 'camera_ip' => $oldItem['camera_ip'], + 'msg_content' => $oldItem['msg_content'] + ]; + $index += 1; + } + ); + return $data; + } + + /** + * @return array + */ + public function headings(): array + { + return [ + __('exports.global.index'), + __('exports.notice.alarm_time'), + __('exports.notice.alarm_type'), + __('exports.notice.msg_type'), + __('exports.notice.space_number'), + __('exports.notice.license_plate'), + __('exports.notice.camera_ip'), + __('exports.notice.msg_content') + ]; + } +} diff --git a/app/Http/Controllers/Admin/NoticeController.php b/app/Http/Controllers/Admin/NoticeController.php new file mode 100644 index 0000000..b8ddf05 --- /dev/null +++ b/app/Http/Controllers/Admin/NoticeController.php @@ -0,0 +1,188 @@ +service = $service; + } + + public function index(Request $request): JsonResponse + { + try { + $query = AdminNotice::query(); + + if ($request->has('alarm_type')) { + $alarm_type = $request->input('alarm_type'); + if ($alarm_type) { + $query->where('alarm_type', $alarm_type); + } + } + + if ($request->has('msg_type')) { + $msg_type = $request->input('msg_type'); + if ($msg_type) { + $query->where('msg_type', $msg_type); + } + } + + if ($request->has('start_time') && $request->has('end_time')) { + $start_time = $request->input('start_time'); + $end_time = $request->input('end_time'); + if ($start_time && $end_time) { + $query->whereBetween('alarm_time', [$start_time, $end_time] + ); + } + } + + // 分页 + $page = $request->input('page', 1); + $perPage = $request->input('per_page', 10); + + $columns = [ + 'id', + 'alarm_time', + 'alarm_type', + 'camera_ip', + 'msg_type', + 'space_id' + ]; + $total = $query->count(); + $items = $query->latest()->forPage($page, $perPage)->select( + $columns + )->get()->each(function ($item) { + return $this->service->getItem($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() + ); + } + } + + public function search(): JsonResponse + { + try { + $data = [ + 'alarm_type_list' => get_select_data( + AdminNoticeService::getAlarmType() + ), + 'msg_type_list' => get_select_data( + AdminNoticeService::getMsgType() + ) + ]; + return $this->responseService->success($data); + } catch (Exception $e) { + $m_prefix = __('exception.exception_handler.resource'); + return $this->responseService->systemError( + $m_prefix . ':' . $e->getMessage() + ); + } + } + + public function setting(Request $request): JsonResponse + { + try { + $data = $request->all(); + $rules = [ + 'close_alert' => 'required|in:0,1', + 'close_all_alert' => 'required|in:0,1', + ]; + $messages = [ + 'close_alert.required' => __( + 'validation.notice.c_empty' + ), + 'close_alert.numeric' => __( + 'validation.notice.c_in' + ), + 'close_all_alert.required' => __( + 'validation.notice.c_empty' + ), + 'close_all_alert.numeric' => __( + 'validation.notice.c_in' + ), + ]; + $validator = Validator::make($data, $rules, $messages); + + if ($validator->fails()) { + throw new ValidationException($validator); + } + $this->service->updateConfig($data); + return $this->responseService->success([], + __('admin.operation_successful')); + } catch (Exception $e) { + $m_prefix = __('exception.exception_handler.resource'); + return $this->responseService->systemError( + $m_prefix . ':' . $e->getMessage() + ); + } + } + + public function show(string $id): JsonResponse + { + try { + $columns = [ + 'id', + 'alarm_time', + 'alarm_type', + 'camera_ip', + 'msg_type', + 'space_id', + 'floor_id', + 'camera_id' + ]; + $data = AdminNotice::query()->find($id, $columns)->toArray(); + $data = $this->service->getItem($data); + return $this->responseService->success($data); + } catch (Exception $e) { + return $this->responseService->systemError( + __('exception.get_data_failed') . ':' . $e->getMessage() + ); + } + } + + /** + * @return BinaryFileResponse + */ + public function export(): BinaryFileResponse + { + return Excel::download( + new AdminNoticeExport(), + __('exports.notice.list') . time() . '.xlsx' + ); + } +} diff --git a/app/Models/AdminNotice.php b/app/Models/AdminNotice.php new file mode 100644 index 0000000..ee870de --- /dev/null +++ b/app/Models/AdminNotice.php @@ -0,0 +1,29 @@ + + */ + protected $hidden + = [ + 'updated_at', + 'deleted_at' + ]; + + public function getAlarmTimeAttribute($value): string + { + return get_datetime('datetime', strtotime($value)); + } +} diff --git a/app/Models/ParkingCamera.php b/app/Models/ParkingCamera.php index 31c0fa4..92627a9 100644 --- a/app/Models/ParkingCamera.php +++ b/app/Models/ParkingCamera.php @@ -36,4 +36,9 @@ class ParkingCamera extends Model { return get_datetime('datetime', strtotime($value)); } + + public static function getNumber($id) + { + return self::query()->where('id', $id)->value('number') ?? ''; + } } diff --git a/app/Services/AdminNoticeService.php b/app/Services/AdminNoticeService.php new file mode 100644 index 0000000..080da8d --- /dev/null +++ b/app/Services/AdminNoticeService.php @@ -0,0 +1,147 @@ + 'hint', + 2 => 'support' + ]; + + private static array $msgTypeArr + = [ + 1 => 'touch', + 2 => 'illegal', + 3 => 'offline', + 4 => 'vip', + 5 => 'task' + ]; + + public function __construct(OperationLogService $logService) + { + parent::__construct($logService); + $this->logService->menuTitle = 'alarm_notice'; + } + + /** + * @return array|string[] + */ + public static function getAlarmType(): array + { + $alarmTypeArr = self::$alarmTypeArr; + foreach ($alarmTypeArr as $key => $value) { + $alarmTypeArr[$key] = __('service.notice.' . $value); + } + return $alarmTypeArr; + } + + /** + * @return array|string[] + */ + public static function getMsgType(): array + { + $msgTypeArr = self::$msgTypeArr; + foreach ($msgTypeArr as $key => $value) { + $msgTypeArr[$key] = __('service.notice.' . $value); + } + return $msgTypeArr; + } + + public function getItem($item) + { + $alarmTypeArr = self::getAlarmType(); + $msgTypeArr = self::getMsgType(); + $item['alarm_type'] = $alarmTypeArr[$item['alarm_type']]; + $item['msg_type'] = $msgTypeArr[$item['msg_type']]; + $item['parking_space_number'] = ''; + $item['license_plate'] = ''; + $ParkingSpace = ParkingSpace::query()->findOrFail( + $item['space_id'], + ['number', 'license_plate_id'] + ); + if ($ParkingSpace) { + $item['parking_space_number'] = $ParkingSpace['number']; + $item['license_plate'] = ParkingLicensePlate::getNumber( + $ParkingSpace['license_plate_id'] + ); + } + if (isset($item['floor_id'])) { + $item['floor'] = AdminFloor::getName($item['floor_id']); + unset($item['floor_id']); + } + if (isset($item['camera_id'])) { + $item['camera_number'] = ParkingCamera::getNumber($item['camera_id']); + unset($item['camera_id']); + } + unset($item['space_id']); + return $item; + } + + public function updateConfig($data) + { + try { + DB::beginTransaction(); + $content = [ + 'close_alert' => $data['close_alert'], + 'close_all_alert' => $data['close_all_alert'] + ]; + $model = AdminConfigs::query()->where('name', 'information_setting') + ->first(); + $oldValues = $model->toArray(); + + $model->update([ + 'content' => $content, + 'updated_at' => get_datetime() + ]); + + $this->logService->logUpdated($model, $oldValues, 'config.update'); + + DB::commit(); + return $model; + } catch (Exception $e) { + DB::rollBack(); + throw $e; + } + } + + public static function createData($data, $user_id) + { + $alarm_type = $data['alarm_type']; + $camera_ip = $data['camera_ip']; + $msg_type = $data['msg_type']; + $create = [ + 'alarm_time' => get_datetime(), + 'alarm_type' => $alarm_type, + 'camera_ip' => $camera_ip, + 'msg_type' => $msg_type, + 'is_read' => 0, + 'admin_user_id' => $user_id, + 'created_at' => get_datetime() + ]; + if (isset($data['space_id']) && $data['space_id']) { + $create['space_id'] = $data['space_id']; + } + if (isset($data['floor_id']) && $data['floor_id']) { + $create['floor_id'] = $data['floor_id']; + } + if (isset($data['camera_id']) && $data['camera_id']) { + $create['camera_id'] = $data['camera_id']; + } + if (isset($data['msg_content']) && $data['msg_content']) { + $create['msg_content'] = $data['msg_content']; + } + AdminNotice::query()->create($create); + } +} diff --git a/database/migrations/2026_04_23_143449_create_admin_notice_table.php b/database/migrations/2026_04_23_143449_create_admin_notice_table.php new file mode 100644 index 0000000..ae64ef9 --- /dev/null +++ b/database/migrations/2026_04_23_143449_create_admin_notice_table.php @@ -0,0 +1,41 @@ +id(); + $table->timestamp('alarm_time')->comment('信息时间'); + $table->tinyInteger('alarm_type')->default(0)->comment('信息类型'); + $table->string('camera_ip', 15)->comment('ip'); + $table->tinyInteger('msg_type')->default(0)->comment('信息内容'); + $table->string('msg_content', 255)->default('')->comment('信息内容详情'); + $table->integer('space_id')->nullable()->comment('车位id'); + $table->integer('floor_id')->nullable()->comment('楼层id'); + $table->integer('camera_id')->nullable()->comment('相机设备id'); + $table->tinyInteger('is_read')->default(0)->comment('是否已读'); + $table->integer('admin_user_id')->default(0)->comment('操作员'); + $table->timestamps(); + $table->innoDb(); + $table->index('alarm_type', 'alarm_type'); + $table->index('msg_type', 'msg_type'); + $table->comment('警报 & 消息'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('admin_notice'); + } +}; diff --git a/database/seeders/AdminConfigSeeder.php b/database/seeders/AdminConfigSeeder.php index d3b38dc..c39a35d 100644 --- a/database/seeders/AdminConfigSeeder.php +++ b/database/seeders/AdminConfigSeeder.php @@ -69,6 +69,15 @@ class AdminConfigSeeder extends Seeder ]), 'status' => 1, 'created_at' => $created_at + ],[ + 'title' => '信息设定', + 'name' => 'information_setting', + 'content' => json_encode([ + 'close_alert' => 0, + 'close_all_alert' => 0 + ]), + 'status' => 1, + 'created_at' => $created_at ] ]; } diff --git a/resources/lang/en/exports.php b/resources/lang/en/exports.php index 19d67f7..ddfe47d 100644 --- a/resources/lang/en/exports.php +++ b/resources/lang/en/exports.php @@ -56,5 +56,15 @@ return [ ], 'event_calendar' => [ 'list' => 'Activity Calendar Template' + ], + 'notice' => [ + 'list' => 'Alerts_Notifications', + 'alarm_time' => 'Information Time', + 'alarm_type' => 'Information Type', + 'msg_type' => 'information content', + 'space_number' => 'Parking Number', + 'license_plate' => 'license plate number', + 'camera_ip' => 'IP', + 'msg_content' => 'Information Content Details' ] ]; diff --git a/resources/lang/en/service.php b/resources/lang/en/service.php index bcb3bfd..cb442dc 100644 --- a/resources/lang/en/service.php +++ b/resources/lang/en/service.php @@ -78,5 +78,14 @@ return [ 'pattern_exists' => 'The current activity mode already exists', 'error_status' => 'The current activity status cannot be deleted', 'error_end' => 'The current activity status cannot be ended' + ], + 'notice' => [ + 'hint' => 'Prompt message', + 'support' => 'Support Information', + 'touch' => 'touch the line', + 'illegal' => 'Illegal parking', + 'offline' => 'Device offline', + 'vip' => 'VIP', + 'task' => 'Task switching' ] ]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 9c2c0ec..135a369 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -111,5 +111,9 @@ return [ 'p_empty' => 'Activity mode cannot be empty', 's_empty' => 'The start time cannot be empty', 'e_empty' => 'The end time cannot be empty' + ], + 'notice' => [ + 'c_empty' => 'Update data cannot be empty', + 'c_in' => 'Updating data can only be 0 or 1', ] ]; diff --git a/resources/lang/zh-CN/exports.php b/resources/lang/zh-CN/exports.php index b3b65a7..044b020 100644 --- a/resources/lang/zh-CN/exports.php +++ b/resources/lang/zh-CN/exports.php @@ -56,5 +56,15 @@ return [ ], 'event_calendar' => [ 'list' => '活动行事历模板' + ], + 'notice' => [ + 'list' => '警报通知', + 'alarm_time' => '信息时间', + 'alarm_type' => '信息类型', + 'msg_type' => '信息内容', + 'space_number' => '车位号码', + 'license_plate' => '车牌号码', + 'camera_ip' => 'IP', + 'msg_content' => '信息内容详情' ] ]; diff --git a/resources/lang/zh-CN/service.php b/resources/lang/zh-CN/service.php index dfc52a3..8d3e648 100644 --- a/resources/lang/zh-CN/service.php +++ b/resources/lang/zh-CN/service.php @@ -78,5 +78,14 @@ return [ 'pattern_exists' => '当前活动模式已存在', 'error_status' => '当前活动状态不可删除', 'error_end' => '当前活动状态不可结束' + ], + 'notice' => [ + 'hint' => '提示信息', + 'support' => '支援信息', + 'touch' => '压线', + 'illegal' => '违泊', + 'offline' => '设备离线', + 'vip' => 'VIP', + 'task' => '任务切换' ] ]; diff --git a/resources/lang/zh-CN/validation.php b/resources/lang/zh-CN/validation.php index ddbaf18..e591551 100644 --- a/resources/lang/zh-CN/validation.php +++ b/resources/lang/zh-CN/validation.php @@ -111,5 +111,9 @@ return [ 'p_empty' => '活动模式不能为空', 's_empty' => '开始时间不能为空', 'e_empty' => '结束时间不能为空' + ], + 'notice' => [ + 'c_empty' => '更新数据不能为空', + 'c_in' => '更新数据只能是0或1', ] ]; diff --git a/resources/lang/zh-TW/exports.php b/resources/lang/zh-TW/exports.php index 43384dc..c541e79 100644 --- a/resources/lang/zh-TW/exports.php +++ b/resources/lang/zh-TW/exports.php @@ -56,5 +56,15 @@ return [ ], 'event_calendar' => [ 'list' => '活動行事曆範本' + ], + 'notice' => [ + 'list' => '警報通知', + 'alarm_time' => '資訊時間', + 'alarm_type' => '資訊類型', + 'msg_type' => '資訊內容', + 'space_number' => '車位號碼', + 'license_plate' => '車牌號碼', + 'camera_ip' => 'IP', + 'msg_content' => '資訊內容詳情' ] ]; diff --git a/resources/lang/zh-TW/service.php b/resources/lang/zh-TW/service.php index 0243082..9f47354 100644 --- a/resources/lang/zh-TW/service.php +++ b/resources/lang/zh-TW/service.php @@ -78,5 +78,14 @@ return [ 'pattern_exists' => '當前活動模式已存在', 'error_status' => '當前活動狀態不可删除', 'error_end' => '當前活動狀態不可結束' + ], + 'notice' => [ + 'hint' => '提示資訊', + 'support' => '支援資訊', + 'touch' => '壓線', + 'illegal' => '違泊', + 'offline' => '設備離線', + 'vip' => 'VIP', + 'task' => '任務切換' ] ]; diff --git a/resources/lang/zh-TW/validation.php b/resources/lang/zh-TW/validation.php index 2299bde..ef6c0a8 100644 --- a/resources/lang/zh-TW/validation.php +++ b/resources/lang/zh-TW/validation.php @@ -111,5 +111,9 @@ return [ 'p_empty' => '活動模式不能為空', 's_empty' => '開始時間不能為空', 'e_empty' => '結束時間不能為空' + ], + 'notice' => [ + 'c_empty' => '更新數據不能為空', + 'c_in' => '更新數據只能是0或1', ] ]; diff --git a/routes/admin/api.php b/routes/admin/api.php index 92f5f29..aa7bb67 100644 --- a/routes/admin/api.php +++ b/routes/admin/api.php @@ -24,6 +24,7 @@ use App\Http\Controllers\Admin\ParkingCameraController; use App\Http\Controllers\Admin\ParkingPatternController; use App\Http\Controllers\Admin\ParkingPatternSpaceController; use App\Http\Controllers\Admin\EventCalendarController; +use App\Http\Controllers\Admin\NoticeController; Route::group(['prefix' => 'admin'], function () { @@ -139,6 +140,11 @@ Route::group(['prefix' => 'admin'], function () { // VIP进出记录 Route::get('/vipAccessRecord', [VipAccessRecordController::class, 'index']); Route::get('/vipAccessRecord/{id}', [VipAccessRecordController::class, 'show']); + // 警报&通知 + Route::get('/notice', [NoticeController::class, 'index']); + Route::get('/notice/search', [NoticeController::class, 'search']); + Route::post('/notice/setting', [NoticeController::class, 'setting']); + Route::get('/notice/show/{id}', [NoticeController::class, 'show']); // 系统日志 Route::get('/operationLog/index', [OperationLogController::class, 'index']); @@ -203,4 +209,5 @@ Route::group(['prefix' => 'admin'], function () { Route::get('/pattern/spacesExport', [ParkingPatternController::class, 'importTemplate']); Route::get('/operationLog/export', [OperationLogController::class, 'export']); Route::get('/eventCalendar/export', [EventCalendarController::class, 'importTemplate']); + Route::get('/notice/export', [NoticeController::class, 'export']); });