import { useEffect, useMemo, useState } from "react"; import { api, ApiError } from "../api"; import type { Category, CategoryInput } from "../types"; import { FolderTree, Pencil, Plus, Trash2 } from "lucide-react"; type FormState = { mode: "create" | "edit"; id?: string; name_zh: string; name_en: string; slug: string; parent_id: string; // "" = top level gpc_brick_code: string; }; function emptyForm(parentId = ""): FormState { return { mode: "create", name_zh: "", name_en: "", slug: "", parent_id: parentId, gpc_brick_code: "", }; } export default function CategoriesPage() { const [rows, setRows] = useState([]); const [error, setError] = useState(""); const [form, setForm] = useState(null); async function load() { setError(""); try { const res = await api.listCategories(); setRows(res.items); } catch (e) { setError(e instanceof ApiError ? e.message : "加载失败"); } } useEffect(() => { load(); }, []); async function remove(c: Category) { if (!confirm(`确认删除分类「${c.name_zh}」(${c.path})?`)) return; setError(""); try { await api.deleteCategory(c.id); await load(); } catch (e) { setError(e instanceof ApiError ? e.message : "删除失败"); } } return (

分类管理

维护商品分类树。英文标识(slug)用于分类路径,新建后不可修改;可重命名、移动层级、删除空分类。

{error && (
{error}
)} {form && ( setForm(null)} onSaved={() => { setForm(null); load(); }} onError={setError} /> )}
{rows.length === 0 ? ( ) : ( rows.map((c) => ( )) )}
名称 路径 GPC 商品数
暂无分类
{c.level > 0 && } {c.name_zh} {c.name_en && {c.name_en}} {c.path} {c.gpc_brick_code || "—"} {c.product_count}
); } function CategoryForm({ form, categories, onClose, onSaved, onError, }: { form: FormState; categories: Category[]; onClose: () => void; onSaved: () => void; onError: (msg: string) => void; }) { const [state, setState] = useState(form); const [busy, setBusy] = useState(false); // When editing, the node itself and its descendants are not valid parents. const parentOptions = useMemo(() => { if (state.mode === "create") return categories; const self = categories.find((c) => c.id === state.id); if (!self) return categories; return categories.filter( (c) => c.id !== self.id && !c.path.startsWith(self.path + "."), ); }, [categories, state.mode, state.id]); function set(key: K, value: FormState[K]) { setState((s) => ({ ...s, [key]: value })); } async function submit() { if (!state.name_zh.trim()) { onError("分类名称不能为空"); return; } setBusy(true); onError(""); const body: CategoryInput = { name_zh: state.name_zh.trim(), name_en: state.name_en.trim() || null, parent_id: state.parent_id || null, gpc_brick_code: state.gpc_brick_code.trim() || null, }; if (state.mode === "create") body.slug = state.slug.trim() || null; try { if (state.mode === "create") { await api.createCategory(body); } else if (state.id) { await api.updateCategory(state.id, body); } onSaved(); } catch (e) { onError(e instanceof ApiError ? e.message : "保存失败"); } finally { setBusy(false); } } return (

{state.mode === "create" ? "新建分类" : "编辑分类"}

{state.mode === "create" ? ( ) : ( )}
); }