"use client"; import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { LazyImage } from "@/components/ui/LazyImage"; interface PendingSkill { id: number; name: string; description: string | null; logoUrl: string | null; sourceUrl: string | null; sourceType: string; categoryId: number | null; features: unknown; tags: unknown; rating: number; createdAt: string; } interface Category { id: number; name: string; } export default function PendingSkillsPage() { const [skills, setSkills] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [categories, setCategories] = useState([]); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); const pageSize = 20; const [message, setMessage] = useState(""); const [selectedIds, setSelectedIds] = useState>(new Set()); const [batchPublishing, setBatchPublishing] = useState(false); const [batchRejecting, setBatchRejecting] = useState(false); const [publishingAll, setPublishingAll] = useState(false); const [editSkill, setEditSkill] = useState(null); const [editForm, setEditForm] = useState({ name: "", description: "", sourceUrl: "", categoryId: "" as string, rating: 0, features: "", tags: "", }); const [saving, setSaving] = useState(false); const fetchSkills = useCallback(async () => { setError(""); try { const res = await fetch(`/api/admin/pending-skills?page=${page}&pageSize=${pageSize}`); const data = await res.json(); setSkills(data.skills || []); setTotal(data.total || 0); } catch { setError("加载待审核技能失败,请稍后重试"); } finally { setLoading(false); } }, [page]); const fetchCategories = useCallback(async () => { try { const res = await fetch("/api/admin/skill-categories"); const data = await res.json(); const flat: Category[] = []; const walk = (list: { id: number; name: string; children?: { id: number; name: string }[] }[]) => { for (const c of list) { flat.push({ id: c.id, name: c.name }); if (Array.isArray(c.children)) walk(c.children as typeof list); } }; walk(data); setCategories(flat); } catch { console.error("Failed to fetch categories"); } }, []); useEffect(() => { fetchSkills(); fetchCategories(); }, [fetchSkills, fetchCategories]); const totalPages = Math.ceil(total / pageSize); const isAllSelected = skills.length > 0 && selectedIds.size === skills.length; const isPartialSelected = selectedIds.size > 0 && selectedIds.size < skills.length; const handleSelectAll = () => { if (isAllSelected) { setSelectedIds(new Set()); } else { setSelectedIds(new Set(skills.map((s) => s.id))); } }; const handleSelect = (id: number) => { const next = new Set(selectedIds); if (next.has(id)) next.delete(id); else next.add(id); setSelectedIds(next); }; const handleBatchPublish = async () => { if (selectedIds.size === 0) return; if (!confirm(`确定批量通过并发布 ${selectedIds.size} 个技能?`)) return; setBatchPublishing(true); try { const res = await fetch("/api/admin/pending-skills/batch-publish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids: Array.from(selectedIds) }), }); const data = await res.json(); if (data.success) { setMessage(`批量发布完成:成功 ${data.published} 个,跳过 ${data.skipped} 个`); fetchSkills(); setSelectedIds(new Set()); } else { setMessage("批量发布失败: " + (data.error || "未知错误")); } } catch { setMessage("批量发布失败"); } finally { setBatchPublishing(false); } }; const handleBatchReject = async () => { if (selectedIds.size === 0) return; if (!confirm(`确定批量拒绝 ${selectedIds.size} 个技能?此操作不可恢复!`)) return; setBatchRejecting(true); try { const res = await fetch("/api/admin/pending-skills/batch-reject", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids: Array.from(selectedIds) }), }); const data = await res.json(); if (data.success) { setMessage(`批量拒绝完成:已拒绝 ${data.rejected} 个,跳过 ${data.skipped} 个`); fetchSkills(); setSelectedIds(new Set()); } else { setMessage("批量拒绝失败: " + (data.error || "未知错误")); } } catch { setMessage("批量拒绝失败"); } finally { setBatchRejecting(false); } }; const handlePublishAll = async () => { if (total === 0) return; if (!confirm(`确定一键通过全部 ${total} 个待审核技能?此操作将发布所有待审核技能到已发布列表。`)) return; if (!confirm("再次确认:此操作不可撤销,确定继续?")) return; setPublishingAll(true); try { const res = await fetch("/api/admin/pending-skills/publish-all", { method: "POST", headers: { "Content-Type": "application/json" }, }); const data = await res.json(); if (data.success) { setMessage(`一键审核完成:共 ${data.total} 个,成功发布 ${data.published} 个,跳过 ${data.skipped} 个`); fetchSkills(); setSelectedIds(new Set()); } else { setMessage("一键审核失败: " + (data.error || "未知错误")); } } catch { setMessage("一键审核失败"); } finally { setPublishingAll(false); } }; const handleReject = async (id: number) => { if (!confirm("确定拒绝该技能?")) return; try { await fetch(`/api/admin/pending-skills/${id}`, { method: "DELETE" }); setSkills((prev) => prev.filter((s) => s.id !== id)); setSelectedIds((prev) => { const n = new Set(prev); n.delete(id); return n; }); } catch { console.error("Reject failed"); } }; const handleQuickPublish = async (id: number) => { if (!confirm("确定通过并发布该技能?")) return; try { const res = await fetch(`/api/admin/pending-skills/${id}/publish`, { method: "POST" }); const data = await res.json(); if (data.success) { setSkills((prev) => prev.filter((s) => s.id !== id)); setSelectedIds((prev) => { const n = new Set(prev); n.delete(id); return n; }); } else { alert(data.error || "发布失败"); } } catch { console.error("Publish failed"); } }; const openEdit = (skill: PendingSkill) => { setEditSkill(skill); setEditForm({ name: skill.name, description: skill.description || "", sourceUrl: skill.sourceUrl || "", categoryId: skill.categoryId?.toString() || "", rating: skill.rating || 0, features: typeof skill.features === "string" ? skill.features : JSON.stringify(skill.features || {}, null, 2), tags: Array.isArray(skill.tags) ? (skill.tags as string[]).join(", ") : String(skill.tags || ""), }); }; const handleSave = async () => { if (!editSkill) return; setSaving(true); try { const parsedFeatures = (() => { try { return JSON.parse(editForm.features); } catch { return editForm.features ? { raw: editForm.features } : null; } })(); const parsedTags = editForm.tags.split(",").map((t) => t.trim()).filter(Boolean); await fetch(`/api/admin/pending-skills/${editSkill.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: editForm.name, description: editForm.description, sourceUrl: editForm.sourceUrl, categoryId: editForm.categoryId ? parseInt(editForm.categoryId) : null, rating: editForm.rating, features: parsedFeatures, tags: parsedTags, }), }); setEditSkill(null); fetchSkills(); } catch { console.error("Save failed"); } finally { setSaving(false); } }; const handlePublish = async () => { if (!editSkill) return; setSaving(true); try { const parsedFeatures = (() => { try { return JSON.parse(editForm.features); } catch { return editForm.features ? { raw: editForm.features } : null; } })(); const parsedTags = editForm.tags.split(",").map((t) => t.trim()).filter(Boolean); await fetch(`/api/admin/pending-skills/${editSkill.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: editForm.name, description: editForm.description, sourceUrl: editForm.sourceUrl, categoryId: editForm.categoryId ? parseInt(editForm.categoryId) : null, rating: editForm.rating, features: parsedFeatures, tags: parsedTags, }), }); const res = await fetch(`/api/admin/pending-skills/${editSkill.id}/publish`, { method: "POST" }); const data = await res.json(); if (data.success) { setEditSkill(null); fetchSkills(); } else { alert(data.error || "发布失败"); } } catch { console.error("Publish failed"); } finally { setSaving(false); } }; const getCategoryName = (id: number | null) => { if (!id) return "未分类"; return categories.find((c) => c.id === id)?.name || "未分类"; }; return (

待审核技能

审核AI发现的GitHub开源技能,编辑后发布上架

{total > 0 && ( )}

{total}

待审核

{skills.filter((s) => s.rating > 0).length}

有Star数据

{selectedIds.size}

已选择

{selectedIds.size > 0 && (
已选择 {selectedIds.size} 个技能
)} {message && (
{message}
)} {error ? (

加载失败

{error}

) : loading ? (
{Array.from({ length: 7 }).map((_, i) => ( ))} {Array.from({ length: 5 }).map((_, i) => ( {Array.from({ length: 7 }).map((_, j) => ( ))} ))}
) : skills.length === 0 ? (

暂无待审核技能

在探索技能页面发现新技能后即可在此审核

) : (
{skills.map((skill) => ( ))}
分类 技能名称 简介 来源 Star 发现时间 操作
{getCategoryName(skill.categoryId)}
{skill.logoUrl ? ( ) : (
{skill.name.charAt(0)}
)} {skill.name}
{skill.description || "-"} {skill.sourceUrl ? ( e.stopPropagation()} > {skill.sourceUrl.replace(/^https?:\/\/github\.com\//, "")} ) : ( - )} {skill.rating > 0 ? ( {skill.rating >= 1000 ? `${(skill.rating / 1000).toFixed(1)}k` : skill.rating} ) : ( - )} {new Date(skill.createdAt).toLocaleDateString("zh-CN")}
)} {totalPages > 1 && (
{page} / {totalPages}
)} !open && setEditSkill(null)}> 编辑技能
setEditForm((f) => ({ ...f, name: e.target.value }))} className="h-11 w-full rounded-xl border border-border/50 bg-white px-4 text-sm text-foreground placeholder:text-muted-foreground/40 focus:border-primary/40 focus:bg-background focus:outline-none focus:ring-2 focus:ring-primary/15 transition-all duration-200" placeholder="输入技能名称" />