'自有车', 'rental' => '租车', ]; public function index(): void { $this->requireLogin(); $db = Database::get(); $familyId = Auth::familyId(); $stmt = $db->prepare('SELECT * FROM vehicles WHERE family_id = ? ORDER BY vehicle_type ASC, is_default DESC, id ASC'); $stmt->execute([$familyId]); $vehicles = $stmt->fetchAll(); // 统计每个车辆被出行方式引用次数 foreach ($vehicles as &$v) { $s = $db->prepare('SELECT COUNT(*) FROM travel_modes WHERE vehicle_id = ?'); $s->execute([$v['id']]); $v['mode_count'] = (int)$s->fetchColumn(); } unset($v); view('vehicles/index', [ 'vehicles' => $vehicles, 'typeLabels' => self::TYPE_LABELS, 'pageTitle' => '车辆管理', ]); } public function create(): void { $this->requireLogin(); view('vehicles/form', [ 'vehicle' => null, 'typeLabels' => self::TYPE_LABELS, 'pageTitle' => '新增车辆', ]); } public function store(): void { $this->requireLogin(); csrf_check(); $db = Database::get(); $familyId = Auth::familyId(); $data = $this->collectInput(); if ($data['error']) { flash('error', $data['error']); redirect('vehicles/create'); } // 车牌唯一性 $chk = $db->prepare('SELECT id FROM vehicles WHERE family_id = ? AND plate_number = ? LIMIT 1'); $chk->execute([$familyId, $data['plate_number']]); if ($chk->fetch()) { flash('error', '该车牌号已存在'); redirect('vehicles/create'); } // 如果是默认自有车,先把其他自有车的 is_default 清掉 if ($data['vehicle_type'] === 'own' && $data['is_default']) { $db->prepare('UPDATE vehicles SET is_default = 0 WHERE family_id = ? AND vehicle_type = "own"') ->execute([$familyId]); } $stmt = $db->prepare('INSERT INTO vehicles (family_id, plate_number, model_name, vehicle_type, rental_company, rental_order_no, is_default, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'); $stmt->execute([ $familyId, $data['plate_number'], $data['model_name'], $data['vehicle_type'], $data['rental_company'], $data['rental_order_no'], $data['is_default'] ? 1 : 0, $data['notes'], ]); flash('error', '创建成功'); redirect('vehicles/index'); } public function edit(): void { $this->requireLogin(); $id = (int)($_GET['id'] ?? 0); $db = Database::get(); $stmt = $db->prepare('SELECT * FROM vehicles WHERE id = ? AND family_id = ?'); $stmt->execute([$id, Auth::familyId()]); $vehicle = $stmt->fetch(); if (!$vehicle) { http_response_code(404); exit('车辆不存在'); } view('vehicles/form', [ 'vehicle' => $vehicle, 'typeLabels' => self::TYPE_LABELS, 'pageTitle' => '编辑车辆', ]); } public function update(): void { $this->requireLogin(); csrf_check(); $db = Database::get(); $familyId = Auth::familyId(); $id = (int)($_POST['id'] ?? 0); $stmt = $db->prepare('SELECT * FROM vehicles WHERE id = ? AND family_id = ?'); $stmt->execute([$id, $familyId]); $old = $stmt->fetch(); if (!$old) { http_response_code(404); exit('车辆不存在'); } $data = $this->collectInput(); if ($data['error']) { flash('error', $data['error']); redirect('vehicles/edit', ['id' => $id]); } // 车牌唯一性(排除自身) $chk = $db->prepare('SELECT id FROM vehicles WHERE family_id = ? AND plate_number = ? AND id != ? LIMIT 1'); $chk->execute([$familyId, $data['plate_number'], $id]); if ($chk->fetch()) { flash('error', '该车牌号已被其他车辆使用'); redirect('vehicles/edit', ['id' => $id]); } // 如果改为默认自有车,清掉其他默认 if ($data['vehicle_type'] === 'own' && $data['is_default']) { $db->prepare('UPDATE vehicles SET is_default = 0 WHERE family_id = ? AND vehicle_type = "own" AND id != ?') ->execute([$familyId, $id]); } $stmt = $db->prepare('UPDATE vehicles SET plate_number = ?, model_name = ?, vehicle_type = ?, rental_company = ?, rental_order_no = ?, is_default = ?, notes = ? WHERE id = ?'); $stmt->execute([ $data['plate_number'], $data['model_name'], $data['vehicle_type'], $data['rental_company'], $data['rental_order_no'], $data['is_default'] ? 1 : 0, $data['notes'], $id, ]); flash('error', '保存成功'); redirect('vehicles/index'); } public function destroy(): void { $this->requireLogin(); csrf_check(); $db = Database::get(); $id = (int)($_POST['id'] ?? 0); $stmt = $db->prepare('SELECT * FROM vehicles WHERE id = ? AND family_id = ?'); $stmt->execute([$id, Auth::familyId()]); $vehicle = $stmt->fetch(); if (!$vehicle) { flash('error', '车辆不存在'); redirect('vehicles/index'); } // 检查是否被出行方式引用 $cnt = $db->prepare('SELECT COUNT(*) FROM travel_modes WHERE vehicle_id = ?'); $cnt->execute([$id]); $usedCount = (int)$cnt->fetchColumn(); if ($usedCount > 0) { flash('error', "该车辆已被 {$usedCount} 条出行方式引用,无法删除"); redirect('vehicles/index'); } // 检查是否被节点直接引用(vehicle_id 在 trip_nodes 上也可能存在) $cnt2 = $db->prepare('SELECT COUNT(*) FROM trip_nodes WHERE vehicle_id = ?'); $cnt2->execute([$id]); $nodeCount = (int)$cnt2->fetchColumn(); if ($nodeCount > 0) { flash('error', "该车辆已被 {$nodeCount} 个节点使用,无法删除"); redirect('vehicles/index'); } $stmt = $db->prepare('DELETE FROM vehicles WHERE id = ?'); $stmt->execute([$id]); flash('error', '删除成功'); redirect('vehicles/index'); } /** * 从 $_POST 收集车辆数据并做基本校验 * @return array {error: string|null, plate_number, model_name, vehicle_type, rental_company, rental_order_no, is_default, notes} */ private function collectInput(): array { $plateNumber = strtoupper(trim($_POST['plate_number'] ?? '')); $modelName = trim($_POST['model_name'] ?? ''); $vehicleType = $_POST['vehicle_type'] ?? 'own'; $rentalCompany = trim($_POST['rental_company'] ?? ''); $rentalOrderNo = trim($_POST['rental_order_no'] ?? ''); $isDefault = !empty($_POST['is_default']); $notes = trim($_POST['notes'] ?? ''); if ($plateNumber === '') { return ['error' => '车牌号不能为空']; } if (mb_strlen($plateNumber) > 20) { return ['error' => '车牌号不能超过 20 个字符']; } if ($modelName === '') { return ['error' => '车型名称不能为空']; } if (!in_array($vehicleType, ['own', 'rental'], true)) { return ['error' => '无效的车辆类型']; } // 自有车不能是默认租车 if ($vehicleType === 'rental') { $isDefault = false; } return [ 'error' => null, 'plate_number' => $plateNumber, 'model_name' => $modelName, 'vehicle_type' => $vehicleType, 'rental_company' => $vehicleType === 'rental' ? ($rentalCompany ?: null) : null, 'rental_order_no' => $vehicleType === 'rental' ? ($rentalOrderNo ?: null) : null, 'is_default' => $isDefault, 'notes' => $notes ?: null, ]; } private function requireLogin(): void { if (!Auth::check()) { redirect('auth/login'); } } }