'自驾(自有车)', 'drive_rental' => '自驾(租车)', 'train' => '火车/高铁', 'plane' => '飞机', 'bus' => '大巴/公交', 'subway' => '地铁', 'taxi' => '出租车/网约车', 'bike' => '骑行', 'walk' => '步行', 'ship' => '轮渡/游船', 'other' => '其他', ]; /** * 出行方式 → 地图/卡片上的图标(emoji)。 * 自驾两类仍用 🚗(本来就是开车);其余按交通工具区分, * 避免路线地图上「一律汽车」让人误解(批 19)。未列入的 mode_type 回退到节点类型图标。 */ public const MODE_ICONS = [ 'drive_own' => '🚗', 'drive_rental' => '🚙', 'train' => '🚄', 'plane' => '✈️', 'bus' => '🚌', 'subway' => '🚇', 'taxi' => '🚕', 'bike' => '🚲', 'walk' => '🚶', 'ship' => '🚢', 'other' => '🔀', ]; public function index(): void { $this->requireLogin(); $db = Database::get(); $familyId = Auth::familyId(); $stmt = $db->prepare(' SELECT tm.*, v.plate_number, v.model_name FROM travel_modes tm LEFT JOIN vehicles v ON v.id = tm.vehicle_id WHERE tm.family_id = ? ORDER BY FIELD(tm.mode_type, "drive_own","drive_rental","train","plane","bus","subway","taxi","bike","walk","ship","other"), tm.id ASC '); $stmt->execute([$familyId]); $modes = $stmt->fetchAll(); foreach ($modes as &$m) { $s = $db->prepare('SELECT COUNT(*) FROM trip_nodes WHERE travel_mode_id = ?'); $s->execute([$m['id']]); $m['node_count'] = (int)$s->fetchColumn(); } unset($m); view('travel_modes/index', [ 'modes' => $modes, 'modeLabels' => self::MODE_LABELS, 'pageTitle' => '出行方式管理', ]); } public function create(): void { $this->requireLogin(); view('travel_modes/form', [ 'mode' => null, 'vehicles' => $this->fetchVehicles(), 'modeLabels' => self::MODE_LABELS, 'pageTitle' => '新增出行方式', ]); } public function store(): void { $this->requireLogin(); csrf_check(); $db = Database::get(); $familyId = Auth::familyId(); $modeType = $_POST['mode_type'] ?? ''; if (!isset(self::MODE_LABELS[$modeType])) { flash('error', '无效的交通类型'); redirect('travel_modes/create'); } $vehicleId = $this->validateVehicle($modeType, $db, $familyId, 'travel_modes/create'); $stmt = $db->prepare('INSERT INTO travel_modes (family_id, mode_type, vehicle_id, notes) VALUES (?, ?, ?, ?)'); $stmt->execute([ $familyId, $modeType, $vehicleId, $this->strOrNull('notes'), ]); flash('error', '创建成功'); redirect('travel_modes/index'); } public function edit(): void { $this->requireLogin(); $id = (int)($_GET['id'] ?? 0); $db = Database::get(); $stmt = $db->prepare('SELECT * FROM travel_modes WHERE id = ? AND family_id = ?'); $stmt->execute([$id, Auth::familyId()]); $mode = $stmt->fetch(); if (!$mode) { http_response_code(404); exit('出行方式不存在'); } view('travel_modes/form', [ 'mode' => $mode, 'vehicles' => $this->fetchVehicles(), 'modeLabels' => self::MODE_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 travel_modes WHERE id = ? AND family_id = ?'); $stmt->execute([$id, $familyId]); if (!$stmt->fetch()) { http_response_code(404); exit('出行方式不存在'); } $modeType = $_POST['mode_type'] ?? ''; if (!isset(self::MODE_LABELS[$modeType])) { flash('error', '无效的交通类型'); redirect('travel_modes/edit', ['id' => $id]); } $vehicleId = $this->validateVehicle($modeType, $db, $familyId, 'travel_modes/edit', ['id' => $id]); $stmt = $db->prepare('UPDATE travel_modes SET mode_type = ?, vehicle_id = ?, notes = ? WHERE id = ?'); $stmt->execute([ $modeType, $vehicleId, $this->strOrNull('notes'), $id, ]); flash('error', '保存成功'); redirect('travel_modes/index'); } public function destroy(): void { $this->requireLogin(); csrf_check(); $db = Database::get(); $id = (int)($_POST['id'] ?? 0); $stmt = $db->prepare('SELECT * FROM travel_modes WHERE id = ? AND family_id = ?'); $stmt->execute([$id, Auth::familyId()]); if (!$stmt->fetch()) { flash('error', '出行方式不存在'); redirect('travel_modes/index'); } $cnt = $db->prepare('SELECT COUNT(*) FROM trip_nodes WHERE travel_mode_id = ?'); $cnt->execute([$id]); $usedCount = (int)$cnt->fetchColumn(); if ($usedCount > 0) { flash('error', "该出行方式已被 {$usedCount} 个节点使用,无法删除"); redirect('travel_modes/index'); } $stmt = $db->prepare('DELETE FROM travel_modes WHERE id = ?'); $stmt->execute([$id]); flash('error', '删除成功'); redirect('travel_modes/index'); } private function validateVehicle(string $modeType, \PDO $db, int $familyId, string $route, array $params = []): ?int { if (!in_array($modeType, ['drive_own', 'drive_rental'], true)) { return null; } $vehicleId = (int)($_POST['vehicle_id'] ?? 0); if ($vehicleId <= 0) { flash('error', '自驾类型必须选择车辆'); redirect($route, $params); } $chk = $db->prepare('SELECT id, vehicle_type FROM vehicles WHERE id = ? AND family_id = ?'); $chk->execute([$vehicleId, $familyId]); $veh = $chk->fetch(); if (!$veh) { flash('error', '车辆不存在'); redirect($route, $params); } if ($modeType === 'drive_own' && $veh['vehicle_type'] !== 'own') { flash('error', '自有车类型必须选择自有车辆'); redirect($route, $params); } if ($modeType === 'drive_rental' && $veh['vehicle_type'] !== 'rental') { flash('error', '租车类型必须选择租用车辆'); redirect($route, $params); } return $vehicleId; } private function fetchVehicles(): array { $stmt = Database::get()->prepare('SELECT * FROM vehicles WHERE family_id = ? ORDER BY vehicle_type ASC, is_default DESC, id ASC'); $stmt->execute([Auth::familyId()]); return $stmt->fetchAll(); } private function strOrNull(string $key): ?string { $v = trim($_POST[$key] ?? ''); return $v === '' ? null : $v; } private function requireLogin(): void { if (!Auth::check()) { redirect('auth/login'); } } }