prepare('SELECT * FROM role_tags WHERE family_id = ? ORDER BY id ASC'); $stmt->execute([$familyId]); $tags = $stmt->fetchAll(); // 每个标签被多少个节点的参与人引用(role_tag_ids 是 JSON 数组) foreach ($tags as &$t) { $s = $db->prepare('SELECT COUNT(*) FROM node_members WHERE role_tag_ids IS NOT NULL AND JSON_CONTAINS(role_tag_ids, ?)'); $s->execute([(string)(int)$t['id']]); $t['used_count'] = (int)$s->fetchColumn(); } unset($t); view('role_tags/index', [ 'tags' => $tags, 'pageTitle' => '角色标签管理', ]); } public function create(): void { Access::requireAdmin(); view('role_tags/form', ['tag' => null, 'pageTitle' => '新增角色标签']); } public function store(): void { Access::requireAdmin(); csrf_check(); $db = Database::get(); $familyId = Access::familyId(); $name = trim($_POST['name'] ?? ''); $icon = trim($_POST['icon'] ?? ''); if ($name === '') { flash('error', '名称不能为空'); redirect('role_tags/create'); } if (mb_strlen($name) > 30) { flash('error', '名称不能超过 30 个字符'); redirect('role_tags/create'); } if ($icon !== '' && mb_strlen($icon) > 4) { flash('error', '图标最多放一个表情'); redirect('role_tags/create'); } $chk = $db->prepare('SELECT id FROM role_tags WHERE family_id = ? AND name = ? LIMIT 1'); $chk->execute([$familyId, $name]); if ($chk->fetch()) { flash('error', '该角色名称已存在'); redirect('role_tags/create'); } $stmt = $db->prepare('INSERT INTO role_tags (family_id, name, icon) VALUES (?, ?, ?)'); $stmt->execute([$familyId, $name, $icon ?: null]); flash('error', '创建成功'); redirect('role_tags/index'); } public function edit(): void { Access::requireAdmin(); view('role_tags/form', [ 'tag' => $this->fetchTag((int)($_GET['id'] ?? 0)), 'pageTitle' => '编辑角色标签', ]); } public function update(): void { Access::requireAdmin(); csrf_check(); $db = Database::get(); $familyId = Access::familyId(); $tag = $this->fetchTag((int)($_POST['id'] ?? 0)); $id = (int)$tag['id']; $name = trim($_POST['name'] ?? ''); $icon = trim($_POST['icon'] ?? ''); if ($name === '') { flash('error', '名称不能为空'); redirect('role_tags/edit', ['id' => $id]); } if (mb_strlen($name) > 30) { flash('error', '名称不能超过 30 个字符'); redirect('role_tags/edit', ['id' => $id]); } if ($icon !== '' && mb_strlen($icon) > 4) { flash('error', '图标最多放一个表情'); redirect('role_tags/edit', ['id' => $id]); } $chk = $db->prepare('SELECT id FROM role_tags WHERE family_id = ? AND name = ? AND id != ? LIMIT 1'); $chk->execute([$familyId, $name, $id]); if ($chk->fetch()) { flash('error', '该角色名称已被其他角色使用'); redirect('role_tags/edit', ['id' => $id]); } $stmt = $db->prepare('UPDATE role_tags SET name = ?, icon = ? WHERE id = ?'); $stmt->execute([$name, $icon ?: null, $id]); flash('error', '保存成功'); redirect('role_tags/index'); } /** * 删除角色标签:被引用时不阻止,而是先把它从各节点参与人身上摘掉 * (角色标签是轻量配置,删不掉会让人很憋屈;节点参与人本身不受影响) */ public function destroy(): void { Access::requireAdmin(); csrf_check(); $db = Database::get(); $tag = $this->fetchTag((int)($_POST['id'] ?? 0)); $id = (int)$tag['id']; $stmt = $db->prepare('SELECT id, role_tag_ids FROM node_members WHERE role_tag_ids IS NOT NULL AND JSON_CONTAINS(role_tag_ids, ?)'); $stmt->execute([(string)$id]); $affected = $stmt->fetchAll(); $upd = $db->prepare('UPDATE node_members SET role_tag_ids = ? WHERE id = ?'); foreach ($affected as $row) { $ids = json_decode((string)$row['role_tag_ids'], true); $ids = is_array($ids) ? array_filter(array_map('intval', $ids), static fn($v) => $v !== $id) : []; $upd->execute([$ids ? json_encode(array_values($ids)) : null, (int)$row['id']]); } $stmt = $db->prepare('DELETE FROM role_tags WHERE id = ? AND family_id = ?'); $stmt->execute([$id, Access::familyId()]); $msg = '删除成功'; if ($affected) { $msg .= ';已同时从 ' . count($affected) . ' 处参与人记录中移除该角色'; } flash('error', $msg); redirect('role_tags/index'); } private function fetchTag(int $id): array { $stmt = Database::get()->prepare('SELECT * FROM role_tags WHERE id = ? AND family_id = ?'); $stmt->execute([$id, Access::familyId()]); $tag = $stmt->fetch(); if (!$tag) { http_response_code(404); exit('角色标签不存在'); } return $tag; } }