Browse Source

停车场管理

master
wanghongjun 3 weeks ago
parent
commit
eaa1c61332
  1. 275
      app/Http/Controllers/Admin/ParkingAccessRecordController.php
  2. 240
      app/Http/Controllers/Admin/ParkingManagementController.php
  3. 15
      app/Http/Controllers/Admin/UserController.php
  4. 53
      app/Models/Parking.php
  5. 12
      app/Services/AdminFloorService.php
  6. 11
      app/Services/BaseService.php
  7. 151
      app/Services/ParkingManagementService.php
  8. 9
      database/migrations/2026_01_29_082121_create_parking_table.php
  9. 20
      database/seeders/AdminMenuSeeder.php
  10. 9
      resources/lang/en/validation.php
  11. 9
      resources/lang/zh-CN/validation.php
  12. 9
      resources/lang/zh-TW/validation.php
  13. 9
      routes/admin/api.php

275
app/Http/Controllers/Admin/ParkingAccessRecordController.php

@ -0,0 +1,275 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\AdminFloor;
use App\Models\ParkingAccessRecord;
use App\Models\ParkingLicensePlate;
use App\Models\ParkingSpace;
use App\Models\ParkingSpaceType;
use App\Models\ParkingSpaceTypeAttr;
use App\Services\OperationLogService;
use App\Services\ParkingLicensePlateService;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ParkingAccessRecordController extends BaseController
{
protected string $menuUri = '';
public function index(Request $request): JsonResponse
{
try {
$query = ParkingAccessRecord::query();
if ($request->has('floor_id')) {
$floor_id = $request->input('floor_id');
if ($floor_id) {
$query->where('floor_id', $floor_id);
}
}
if ($request->has('license_plate')) {
$license_plate = $request->input('license_plate');
if ($license_plate) {
$license_plate_id = ParkingLicensePlate::getValueId(
$license_plate
);
if ($license_plate_id) {
$query->where('license_plate_id', $license_plate_id);
} else {
$query->where('id', 0);
}
}
}
if ($request->has('parking_space_number')) {
$parking_space_number = $request->input('parking_space_number');
if ($parking_space_number) {
$space_id = ParkingSpace::getValueId($parking_space_number);
if ($space_id) {
$query->where('space_id', $space_id);
} else {
$query->where('id', 0);
}
}
}
if ($request->has('parking_space_type')) {
$parking_space_type = $request->input('parking_space_type');
if ($parking_space_type) {
$query->where('space_type', $parking_space_type);
}
}
if ($request->has('parking_space_attr')) {
$parking_space_attr = $request->input('parking_space_attr');
if ($parking_space_attr) {
$query->where('space_attr', $parking_space_attr);
}
}
if ($request->has('status')) {
$status = $request->input('status');
if ($status) {
$query->where('status', $status);
}
}
if ($request->has('start_enter_time')
&& $request->has(
'end_enter_time'
)
) {
$start_enter_time = $request->input('start_enter_time');
$end_enter_time = $request->input('end_enter_time');
if ($start_enter_time && $end_enter_time) {
$query->whereBetween(
'enter_time',
[$start_enter_time, $end_enter_time]
);
}
}
if ($request->has('start_leave_time')
&& $request->has(
'end_leave_time'
)
) {
$start_leave_time = $request->input('start_leave_time');
$end_leave_time = $request->input('end_leave_time');
if ($start_leave_time && $end_leave_time) {
$query->whereBetween(
'enter_time',
[$start_leave_time, $end_leave_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 $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 enter(Request $request)
{
try {
DB::beginTransaction();
$data = $request->all();
$floor = $data['floor'];
$license_plate = $data['license_plate'];
$space_number = $data['space_number'];
$enter_time = $data['enter_time'] ?? date("Y-m-d H:i:s", time());
// 添加车牌
$floor_id = AdminFloor::query()->where('name', $floor)->value('id');
$Space = ParkingSpace::query()->where(['number' => $space_number])->first();
$space_type_id = $Space['space_type_id'];
$space_attr = $Space['space_attr_id'];
$space_id = $Space['id'];
$license_plate_id = ParkingLicensePlate::getValueId($license_plate);
if (!$license_plate_id) {
(new ParkingLicensePlateService(new OperationLogService()))->createData([
'number' => $license_plate,
'space_type_id' => $space_type_id
]);
}
// 先 添加记录
ParkingAccessRecord::query()->create([
'floor_id' => $floor_id,
'license_plate_id' => $license_plate_id,
'space_id' => $space_id,
'space_type' => $space_type_id,
'space_attr' => $space_attr,
'status' => 1,
'enter_time' => $enter_time,
'created_at' => date("Y-m-d H:i:s", time())
]);
// 修改车位
$Space->update([
'license_plate_id' => $license_plate_id,
'berthing_time' => $enter_time,
'recognition' => rand(80, 100),
'status' => 1
]);
// 车灯变化
$SpaceType = ParkingSpaceType::query()->find($space_type_id);
$SpaceTypeAttr = ParkingSpaceTypeAttr::getSpaceTypeAttrInfo($space_type_id, $space_attr);
$color_occupy = $SpaceType['default_color_occupy'];
$color_vacant = $SpaceType['default_color_vacant'];
$is_flicker = $SpaceType['default_is_warning'];
if ($SpaceTypeAttr) {
$color_occupy = $SpaceTypeAttr['color_occupy'];
$color_vacant = $SpaceTypeAttr['color_vacant'];
$is_flicker = $SpaceTypeAttr['is_warning'];
}
DB::commit();
} catch (Exception $e) {
DB::rollBack();
}
}
protected function getBody($color_occupy, $color_vacant, $is_flicker)
{
$is_flicker = (bool)$is_flicker;
$body = [
"lampType" => "internal",
"VehicleNumber" => [
"VehicleChannelNum" => 1,
"VehicleChannelList" => [
[
"LampSoure" => "internal", // unrelated
"VehicleNoExist" => [
"enabled" => true,
"flashEnabled" => $is_flicker,
"lampColor" => $color_occupy
],
"VehicleExist" => [
"enabled" => true,
"flashEnabled" => $is_flicker,
"lampColor" => $color_vacant
]
]
]
],
"replaceLampCtrl" => [
"enabled" => false,
"ipV4Address" => "0.0.0.0",
"portNo" => 80,
"username" => "admin",
"password" => "dddf4589f3430f7b7395bb9d35e05b27"
],
"lampStatusArr" => [
[
"name" => "内置灯",
"value" => "相机自控",
"\$\$hashKey" => "062"
],
[
"name" => "外置灯1",
"value" => "相机自控",
"\$\$hashKey" => "063"
],
[
"name" => "外置灯2",
"value" => "相机自控",
"\$\$hashKey" => "064"
],
[
"name" => "外置灯3",
"value" => "相机自控",
"\$\$hashKey" => "065"
]
]
];
return $body;
}
public function leave(Request $request)
{
$data = $request->all();
$floor_id = $data['floor_id'];
$license_plate = $data['license_plate'];
$space_id = $data['space_id'];
$status = $data['status'] ?? 1;
$leave_time = $data['leave_time'] ?? get_datetime();
// 先修改记录
// 车灯变化
}
}

240
app/Http/Controllers/Admin/ParkingManagementController.php

@ -0,0 +1,240 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Exceptions\CustomException;
use App\Models\Parking;
use App\Services\AdminTranslationService;
use App\Services\ApiResponseService;
use App\Services\ParkingManagementService;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class ParkingManagementController extends BaseController
{
protected string $menuUri = 'parkingManagement';
protected ParkingManagementService $service;
/**
* 构造函数
* @param ApiResponseService $responseService
* @param ParkingManagementService $service
*/
public function __construct(
ApiResponseService $responseService,
ParkingManagementService $service
) {
parent::__construct($responseService);
$this->service = $service;
}
public function index(Request $request): JsonResponse
{
try {
$query = Parking::query();
// 分页
$page = $request->input('page', 1);
$perPage = $request->input('per_page', 10);
$statusArr = ParkingManagementService::getStatus();
$total = $query->count();
$items = $query->latest()->forPage($page, $perPage)->select()
->get()->each(function ($item) use ($statusArr) {
$tr_name = AdminTranslationService::getTranslationName(
$item['id'],
1
);
$item['name'] = $tr_name ?: $item['name'];
$open_time_res = $this->service->optionTime(
$item['open_time']
);
$item['open_time'] = $open_time_res['time'];
$item['open_time_str'] = $open_time_res['str'];
$close_time_res = $this->service->optionTime(
$item['close_time']
);
$item['close_time'] = $close_time_res['time'];
$item['close_time_str'] = $close_time_res['str'];
$item['parking_space_count']
= $this->service->getParkingCount($item['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()
);
}
}
/**
* @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',
'open_time' => 'required|size:5',
'close_time' => 'required|size:5',
'address' => 'required',
];
$messages = [
'name.required' => __(
'validation.parking_management.n_empty'
),
'open_time.required' => __(
'validation.parking_management.o_empty'
),
'open_time.size' => __(
'validation.parking_management.o_length'
),
'close_time.required' => __(
'validation.parking_management.c_empty'
),
'close_time.size' => __(
'validation.parking_management.c_length'
),
'address.length' => __(
'validation.parking_management.a_empty'
),
];
$model = Parking::query();
if ($id) {
$this->validateId($id, Parking::class);
$model->where([
['name', '=', $data['name']],
['id', '<>', $id]
]);
} else {
$model->where('name', $data['name']);
}
if ($model->exists()) {
throw new CustomException(
__('validation.parking_management.name_exists')
);
}
$validator = Validator::make($data, $rules, $messages);
if ($validator->fails()) {
throw new ValidationException($validator);
}
$open_times = strtotime($data['open_time']);
$close_time = strtotime($data['close_time']);
if ($open_times >= $close_time) {
throw new CustomException(
__('validation.parking_management.error_time')
);
}
}
public function edit($id): JsonResponse
{
try {
$this->validateId($id, Parking::class);
$item = Parking::query()->findOrFail($id);
$data = [
'item' => $item
];
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 {
$data = $request->all();
$this->saveValidator($data, $id);
$this->service->updateModel($data, $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 ValidationException
*/
public function destroy(string $id): JsonResponse
{
try {
$this->validateId($id, Parking::class);
$this->service->deleteModel($id);
return $this->responseService->success(
null,
__('admin.delete_succeeded')
);
} catch (ValidationException $e) {
throw $e;
} catch (Exception $e) {
return $this->responseService->systemError(
__('admin.operation_failed') . ':'
. $e->getMessage()
);
}
}
}

15
app/Http/Controllers/Admin/UserController.php

@ -5,15 +5,14 @@ namespace App\Http\Controllers\Admin;
use App\Exceptions\CustomException;
use App\Models\AdminRoles;
use App\Models\AdminUsers;
use App\Models\Parking;
use App\Services\ApiResponseService;
use App\Services\AdminUsersService;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Psr\SimpleCache\InvalidArgumentException;
class UserController extends BaseController
{
@ -98,17 +97,7 @@ class UserController extends BaseController
protected function getPackingList(): array
{
return [
[
'id' => 1,
'name' => '停车场1'
],
[
'id' => 2,
'name' => '停车场1'
]
];
return Parking::getData();
}
/**

53
app/Models/Parking.php

@ -2,10 +2,61 @@
namespace App\Models;
use App\Services\AdminTranslationService;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Parking extends Model
{
use HasFactory;
use HasFactory, SoftDeletes;
protected $table = 'parking';
protected $fillable
= [
'name',
'open_time',
'close_time',
'address',
'status'
];
protected $hidden
= [
'updated_at',
'deleted_at'
];
public function getCreatedAtAttribute($value): string
{
return $value ? date("Y-m-d H:i:s", strtotime($value)) : $value;
}
public static function getName($id)
{
$tr_name = AdminTranslationService::getTranslationName($id, 3);
$name = self::query()->where('id', $id)->value('name') ?? '';
return $tr_name ?: $name;
}
public static function getValueId($number)
{
return self::query()->where('number', $number)->value('id') ?? '';
}
public static function getData()
{
$columns = ['id', 'name'];
return self::query()->where('status', 1)->select($columns)->get()->each(
function ($item) {
$tr_name = AdminTranslationService::getTranslationName(
$item['id'],
3
);
$item['name'] = $tr_name ?: $item['name'];
return $item;
}
)->toArray() ?? [];
}
}

12
app/Services/AdminFloorService.php

@ -4,9 +4,8 @@ namespace App\Services;
use App\Models\AdminFloor;
use App\Models\AdminFloorRegion;
use App\Models\Parking;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class AdminFloorService
@ -210,9 +209,14 @@ class AdminFloorService
return $data;
}
// 获取楼栋层
// 获取停车场
public static function getBuildingFloor(): array
{
return self::$buildingFloor;
$parkingData = Parking::getData();
$data = [];
foreach ($parkingData as $item) {
$data[$item['id']] = $item['name'];
}
return $data;
}
}

11
app/Services/BaseService.php

@ -4,6 +4,11 @@ namespace App\Services;
class BaseService
{
/**
* @var string
*/
protected string $menuTitle = '';
/**
* @var OperationLogService
*/
@ -16,5 +21,11 @@ class BaseService
public function __construct(OperationLogService $logService)
{
$this->logService = $logService;
$this->logService->menuTitle = $this->menuTitle;
}
protected function saveTranslation($id, $ch, $en, $tw, $type)
{
AdminTranslationService::saveTranslation($ch, $en, $tw, $id, $type);
}
}

151
app/Services/ParkingManagementService.php

@ -0,0 +1,151 @@
<?php
namespace App\Services;
use App\Models\AdminFloor;
use App\Models\Parking;
use App\Models\ParkingSpace;
use Exception;
use Illuminate\Support\Facades\DB;
class ParkingManagementService extends BaseService
{
private static array $statusArr = ['disabled', 'enable'];
protected string $menuTitle = 'parking_management_list';
/**
* @return array|string[]
*/
public static function getStatus(): array
{
$statusArr = self::$statusArr;
foreach ($statusArr as $key => $value) {
$statusArr[$key] = __('admin.' . $value);
}
return $statusArr;
}
// 创建
public function createModel(array $data)
{
try {
DB::beginTransaction();
$model = Parking::query()->create([
'name' => $data['name'],
'open_time' => $data['open_time'],
'close_time' => $data['close_time'],
'address' => $data['address'],
'created_at' => get_datetime()
]);
$this->logService->logCreated($model, 'parking_management.create');
$this->saveTranslation(
$model->id,
$data['name'],
$data['en_name'] ?? '',
$data['tw_name'] ?? '',
3
);
DB::commit();
return $model;
} catch (Exception $e) {
DB::rollBack();
throw $e;
}
}
// 创建
public function updateModel(array $data, $id)
{
try {
DB::beginTransaction();
$model = Parking::query()->findOrFail($id);
$oldValue = $model->toArray();
$update = [
'name' => $data['name'],
'open_time' => $data['open_time'],
'close_time' => $data['close_time'],
'address' => $data['address'],
'status' => $data['status'] ?? 1,
'updated_at' => get_datetime()
];
$model->update($update);
$this->logService->logUpdated(
$model,
$oldValue,
'parking_management.update'
);
$this->saveTranslation(
$id,
$data['name'],
$data['en_name'] ?? '',
$data['tw_name'] ?? '',
3
);
DB::commit();
return $model;
} catch (Exception $e) {
DB::rollBack();
throw $e;
}
}
public function deleteModel(int $id): bool
{
try {
DB::beginTransaction();
$model = Parking::query()->findOrFail($id);
$this->logService->logDeleted(
$model,
'parking_management.delete'
);
$model->delete();
AdminTranslationService::syncDelete($id, 3);
DB::commit();
return true;
} catch (Exception $e) {
DB::rollBack();
throw $e;
}
}
public function getParkingCount($parking_id)
{
$count = 0;
$floorIds = AdminFloor::query()->where('building_floor', $parking_id)
->pluck('id');
if ($floorIds) {
return ParkingSpace::query()->whereIn('floor_id', $floorIds)->count(
);
}
return $count;
}
public function optionTime($time): array
{
$str = 'AM';
$end_str = 'PM';
$arr = explode(':', $time);
if ($arr[0] > 12) {
$time = ($arr[0] - 12) . ':' . $arr[1];
$str = $end_str;
}
return [
'time' => $time,
'str' => $str
];
}
}

9
database/migrations/2026_01_29_082121_create_parking_table.php

@ -13,8 +13,15 @@ return new class extends Migration
{
Schema::create('parking', function (Blueprint $table) {
$table->id();
$table->string('title')->unique();
$table->string('name', 50)->default('')->comment('停车场名称');
$table->char('open_time', 5)->comment('开门时间');
$table->char('close_time', 5)->comment('关门时间');
$table->string('address', 255)->default('')->comment('位置');
$table->tinyInteger('status')->default(1)->comment('状态');
$table->timestamps();
$table->softDeletes();
$table->innoDb();
$table->comment('停车场管理');
});
}

20
database/seeders/AdminMenuSeeder.php

@ -368,6 +368,16 @@ class AdminMenuSeeder extends Seeder
'edit' => 'config.update'
]
],
'floor_plan' => [
'uri' => 'floors',
'page_uri' => '/parkingmanagement/floorManagement',
'child' => [
'read_only' => 'floors.index',
'add_floor' => 'floors.store',
'edit_floor' => 'floors.update',
'delete' => 'floors.destroy'
]
],
'translation' => [
'uri' => 'translations',
'page_uri' => '/system/translation',
@ -434,12 +444,12 @@ class AdminMenuSeeder extends Seeder
],
'channel_management' => [
'uri' => 'channelManagement',
'page_uri' => '/parkingmanagement/equipmentManagement',
'page_uri' => '/parkingmanagement/channelManagement',
'child' => [
'read_only' => 'equipmentManagement.index',
'add' => 'equipmentManagement.store',
'edit' => 'equipmentManagement.update',
'delete' => 'equipmentManagement.destroy'
'read_only' => 'channelManagement.index',
'add' => 'channelManagement.store',
'edit' => 'channelManagement.update',
'delete' => 'channelManagement.destroy'
]
],
'guard_booth_management' => [

9
resources/lang/en/validation.php

@ -116,5 +116,14 @@ return [
'notice' => [
'c_empty' => 'Update data cannot be empty',
'c_in' => 'Updating data can only be 0 or 1',
],
'parking_management' => [
'n_empty' => 'The parking lot name cannot be empty',
'o_empty' => 'The business start time cannot be empty',
'o_length' => 'The length of the business start time must be 5',
'e_empty' => 'The closing time cannot be empty',
'e_length' => 'The length of the business closing time must be 5',
'a_empty' => 'Position cannot be empty',
'error_time' => 'The end time cannot be less than the start time'
]
];

9
resources/lang/zh-CN/validation.php

@ -116,5 +116,14 @@ return [
'notice' => [
'c_empty' => '更新数据不能为空',
'c_in' => '更新数据只能是0或1',
],
'parking_management' => [
'n_empty' => '停车场名称不能为空',
'o_empty' => '营业开始时间不能为空',
'o_length' => '营业开始时间长度必须是5',
'e_empty' => '营业结束时间不能为空',
'e_length' => '营业结束时间长度必须是5',
'a_empty' => '位置不能为空',
'error_time' => '结束时间不能小于开始时间'
]
];

9
resources/lang/zh-TW/validation.php

@ -116,5 +116,14 @@ return [
'notice' => [
'c_empty' => '更新數據不能為空',
'c_in' => '更新數據只能是0或1',
],
'parking_management' => [
'n_empty' => '停車場名稱不能為空',
'o_empty' => '營業開始時間不能為空',
'o_length' => '營業開始時間長度必須是5',
'e_empty' => '營業結束時間不能為空',
'e_length' => '營業結束時間長度必須是5',
'a_empty' => '位置不能為空',
'error_time' => '結束時間不能小於開始時間'
]
];

9
routes/admin/api.php

@ -27,11 +27,13 @@ use App\Http\Controllers\Admin\EventCalendarController;
use App\Http\Controllers\Admin\NoticeController;
use App\Http\Controllers\Admin\LicensePlateRecognitionController;
use App\Http\Controllers\Admin\ParkingBehaviorController;
use App\Http\Controllers\Admin\ParkingManagementController;
Route::group(['prefix' => 'admin'], function () {
// 认证相关接口
Route::post('/login', [AuthController::class, 'login']);
Route::get('/isapi', [AuthController::class, 'isApi']);
// get测试区
Route::get('parkingSpace/create', [ParkingSpaceController::class, 'store']);
@ -205,6 +207,13 @@ Route::group(['prefix' => 'admin'], function () {
Route::put('/translations/{id}', [TranslationController::class, 'update']);
Route::delete('/translations/{id}', [TranslationController::class, 'destroy']);
Route::get('/translations/rule', [TranslationController::class, 'rule']);
// 停车场管理
Route::get('/parkingManagement', [ParkingManagementController::class, 'index']);
Route::post('/parkingManagement', [ParkingManagementController::class, 'store']);
Route::get('/parkingManagement/edit/{id}', [ParkingManagementController::class, 'edit']);
Route::put('/parkingManagement/{id}', [ParkingManagementController::class, 'update']);
Route::delete('/parkingManagement/{id}', [ParkingManagementController::class, 'destroy']);
Route::get('/parkingManagement/rule', [ParkingManagementController::class, 'rule']);
// 角色
Route::get('/roles', [RolesController::class, 'index']);
Route::get('/roles/create', [RolesController::class, 'create']);

Loading…
Cancel
Save