requireAdmin(); $db = Database::get(); $familyId = Auth::familyId(); $stmt = $db->prepare('SELECT * FROM node_types WHERE family_id = ? ORDER BY is_system DESC, id ASC'); $stmt->execute([$familyId]); $types = $stmt->fetchAll(); // 统计每个类型被引用的节点数 foreach ($types as &$t) { $s = $db->prepare('SELECT COUNT(*) FROM trip_nodes WHERE node_type_id = ?'); $s->execute([$t['id']]); $t['node_count'] = (int)$s->fetchColumn(); } unset($t); view('node_types/index', [ 'types' => $types, 'pageTitle' => '节点类型管理', ]); } public function create(): void { $this->requireAdmin(); view('node_types/form', [ 'type' => null, 'pageTitle' => '新增节点类型', ]); } public function store(): void { $this->requireAdmin(); csrf_check(); $db = Database::get(); $familyId = Auth::familyId(); $name = trim($_POST['name'] ?? ''); $icon = trim($_POST['icon'] ?? ''); if ($name === '') { flash('error', '名称不能为空'); redirect('node_types/create'); } if (mb_strlen($name) > 30) { flash('error', '名称不能超过 30 个字符'); redirect('node_types/create'); } // 唯一性校验 $chk = $db->prepare('SELECT id FROM node_types WHERE family_id = ? AND name = ? LIMIT 1'); $chk->execute([$familyId, $name]); if ($chk->fetch()) { flash('error', '该类型名称已存在'); redirect('node_types/create'); } $stmt = $db->prepare('INSERT INTO node_types (family_id, name, icon, is_system) VALUES (?, ?, ?, FALSE)'); $stmt->execute([$familyId, $name, $icon ?: null]); flash('error', '创建成功'); redirect('node_types/index'); } public function edit(): void { $this->requireAdmin(); $id = (int)($_GET['id'] ?? 0); $db = Database::get(); $stmt = $db->prepare('SELECT * FROM node_types WHERE id = ? AND family_id = ?'); $stmt->execute([$id, Auth::familyId()]); $type = $stmt->fetch(); if (!$type) { http_response_code(404); exit('节点类型不存在'); } view('node_types/form', [ 'type' => $type, 'pageTitle' => '编辑节点类型', ]); } public function update(): void { $this->requireAdmin(); csrf_check(); $db = Database::get(); $familyId = Auth::familyId(); $id = (int)($_POST['id'] ?? 0); $stmt = $db->prepare('SELECT * FROM node_types WHERE id = ? AND family_id = ?'); $stmt->execute([$id, $familyId]); $type = $stmt->fetch(); if (!$type) { http_response_code(404); exit('节点类型不存在'); } $name = trim($_POST['name'] ?? ''); $icon = trim($_POST['icon'] ?? ''); if ($name === '') { flash('error', '名称不能为空'); redirect('node_types/edit', ['id' => $id]); } if (mb_strlen($name) > 30) { flash('error', '名称不能超过 30 个字符'); redirect('node_types/edit', ['id' => $id]); } // 唯一性检查(排除自身) $chk = $db->prepare('SELECT id FROM node_types WHERE family_id = ? AND name = ? AND id != ? LIMIT 1'); $chk->execute([$familyId, $name, $id]); if ($chk->fetch()) { flash('error', '该类型名称已被其他类型使用'); redirect('node_types/edit', ['id' => $id]); } $stmt = $db->prepare('UPDATE node_types SET name = ?, icon = ? WHERE id = ?'); $stmt->execute([$name, $icon ?: null, $id]); flash('error', '保存成功'); redirect('node_types/index'); } public function destroy(): void { $this->requireAdmin(); csrf_check(); $db = Database::get(); $id = (int)($_POST['id'] ?? 0); $stmt = $db->prepare('SELECT * FROM node_types WHERE id = ? AND family_id = ?'); $stmt->execute([$id, Auth::familyId()]); $type = $stmt->fetch(); if (!$type) { flash('error', '节点类型不存在'); redirect('node_types/index'); } if ((int)$type['is_system'] === 1) { flash('error', '系统内置类型不能删除'); redirect('node_types/index'); } // 检查是否被引用 $cnt = $db->prepare('SELECT COUNT(*) FROM trip_nodes WHERE node_type_id = ?'); $cnt->execute([$id]); $usedCount = (int)$cnt->fetchColumn(); if ($usedCount > 0) { flash('error', "该类型已被 {$usedCount} 个节点使用,无法删除"); redirect('node_types/index'); } $stmt = $db->prepare('DELETE FROM node_types WHERE id = ?'); $stmt->execute([$id]); flash('error', '删除成功'); redirect('node_types/index'); } private function requireAdmin(): void { if (!Auth::isAdmin()) { http_response_code(403); exit('仅管理员可访问此页面'); } } }