/* global React */ const { useState, useMemo, useEffect, useRef } = React; /* ===== Sample geometries (mock data; replace with /api/geometries later) ===== */ const SAMPLE_GEOMETRIES = [ { id: "20260512-203200-a8b4f1", name: "盘式制动器装配体", fmt: "cdb", size: 12582912, desc: "4 部件装配:刹车盘 + 内/外刹车片 + 背板。共 1 个 contact pair,结构钢材料。PyAnsys 官方 td-1 示例。", original: "disc_pad_model.cdb", source_url: "https://github.com/ansys/example-data/.../td-1/disc_pad_model.cdb", at: "2026-05-12 20:32", by: "admin", refs: [ { case_id: "c-20260510-a1", name: "盘式制动器 · 线性模态", atype: "modal" }, { case_id: "c-20260510-a2", name: "盘式制动器 · 部分预应力", atype: "static_structural" }, { case_id: "c-20260510-a3", name: "盘式制动器 · 全非线性", atype: "nonlinear" }, { case_id: "c-20260510-a4", name: "盘式制动器 · 参数化扫频", atype: "harmonic" }, ] }, { id: "20260511-091245-c2e891", name: "涡轮叶片 NACA-65", fmt: "step", size: 3354624, desc: "压气机第一级转子叶片单通道模型,含榫头与平台。来自客户 KCT-A-2024 项目。", original: "turbine_blade_naca65.step", at: "2026-05-11 09:12", by: "admin", refs: [{ case_id: "c-20260511-b1", name: "叶片 · 离心载荷静力", atype: "static_structural" }] }, { id: "20260510-160340-9d33aa", name: "压力容器壳体", fmt: "iges", size: 4718592, desc: "薄壁圆筒 + 半球封头,含两个法兰开孔。ASME 第八章设计校核基础几何。", original: "pressure_vessel.iges", at: "2026-05-10 16:03", by: "admin", refs: [ { case_id: "c-20260510-c1", name: "容器 · 内压稳态", atype: "static_structural" }, { case_id: "c-20260510-c2", name: "容器 · 屈曲分析", atype: "buckling" }, { case_id: "c-20260510-c3", name: "容器 · 热应力耦合", atype: "thermal_structural" }, ] }, { id: "20260509-114220-7f6e1c", name: "齿轮箱壳体", fmt: "cdb", size: 19294617, desc: "二级减速箱箱体,含 4 个轴承座孔位、8 颗螺栓位。模态 + 谐响应分析复用。", original: "gearbox_housing.cdb", at: "2026-05-09 11:42", by: "admin", refs: [{ case_id: "c-20260509-d1", name: "齿轮箱 · 整机模态", atype: "modal" }] }, { id: "20260508-082015-31b7d2", name: "散热翅片阵列", fmt: "anf", size: 1677721, desc: "20 片纵向直翅片,铝合金,用于稳态 / 瞬态热分析对比。", original: "heatsink_fins.anf", at: "2026-05-08 08:20", by: "admin", refs: [] }, { id: "20260507-153842-5a90b8", name: "螺栓 M16 装配", fmt: "step", size: 629145, desc: "GB/T 5783 标准件,含螺母与垫片。预紧 + 接触分析基础几何。", original: "bolt_m16_assembly.step", at: "2026-05-07 15:38", by: "admin", refs: [ { case_id: "c-20260507-e1", name: "螺栓 · 预紧力静力", atype: "static_structural" }, { case_id: "c-20260507-e2", name: "螺栓 · 滑移屈服", atype: "nonlinear" }, ] }, { id: "20260505-094711-2bf041", name: "太阳能板支架", fmt: "iges", size: 6082662, desc: "屋顶分布式光伏组件支架,含立柱、横梁、压块。风载与地震载荷分析。", original: "solar_rack.iges", at: "2026-05-05 09:47", by: "admin", refs: [{ case_id: "c-20260505-f1", name: "支架 · 风载静力", atype: "static_structural" }] }, { id: "20260503-181520-e7c3d9", name: "连接器外壳", fmt: "xt", size: 419430, desc: "塑料注塑件,含 8 个端子孔。落跌冲击分析备用几何。", original: "connector_shell.x_t", at: "2026-05-03 18:15", by: "admin", refs: [] }, ]; const FMT_META = { cdb: { tag: "CDB", name: "ANSYS CDB", cls: "cdb" }, step: { tag: "STEP", name: "STEP", cls: "step" }, stp: { tag: "STP", name: "STEP", cls: "step" }, iges: { tag: "IGES", name: "IGES", cls: "iges" }, igs: { tag: "IGS", name: "IGES", cls: "iges" }, anf: { tag: "ANF", name: "ANSYS Native", cls: "anf" }, xt: { tag: "X_T", name: "Parasolid", cls: "xt" }, xb: { tag: "X_B", name: "Parasolid Bin.", cls: "xt" }, }; const FILTER_BUCKETS = [ { key: "cdb", label: "CDB", match: (g) => g.fmt === "cdb" }, { key: "step", label: "STEP", match: (g) => g.fmt === "step" || g.fmt === "stp" }, { key: "iges", label: "IGES", match: (g) => g.fmt === "iges" || g.fmt === "igs" }, { key: "anf", label: "ANF", match: (g) => g.fmt === "anf" }, { key: "other", label: "其他", match: (g) => ["xt", "xb"].includes(g.fmt) }, { key: "unref", label: "未引用", match: (g) => g.refs.length === 0 }, ]; const fmtSize = (n) => { if (n >= 1048576) return (n/1048576).toFixed(1) + " MB"; if (n >= 1024) return (n/1024).toFixed(1) + " KB"; return n + " B"; }; /* ===== Format art (colored block thumb, the only "preview" we get) ===== */ const FmtArt = ({ fmt, className = "", showLabel = true }) => { const meta = FMT_META[fmt] || FMT_META.xt; return (
{showLabel && {meta.tag}}
); }; const FmtBadge = ({ fmt }) => { const meta = FMT_META[fmt] || FMT_META.xt; return {meta.tag}; }; /* ===== Grid card — name only, footer with fmt + size + ref count ===== */ const GeoCard = ({ g, onPick }) => (
onPick(g)} role="button" tabIndex={0} onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && onPick(g)}> {g.refs.length > 0 && ( ↗ {g.refs.length} )}
{g.name}
{fmtSize(g.size)} {g.at.slice(5, 10)}
); /* ===== List row ===== */ const GeoRow = ({ g, onPick }) => (
onPick(g)} role="button" tabIndex={0} onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && onPick(g)}>
{g.name}
{g.id}
{fmtSize(g.size)} {g.refs.length === 0 ? — 未引用 : <>{g.refs.length} 个 case} {g.at.slice(0, 10)}
); /* ===== Right-side drawer (detail) ===== */ const GeoDrawer = ({ g, onClose, onUse, onDelete }) => { // Lock scroll + ESC to close while open useEffect(() => { if (!g) return; const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [g, onClose]); return ( <>
{g && (
e.stopPropagation()}> {g.has_preview ? ( ) : ( <>
{FMT_META[g.fmt]?.tag}
)}
{g.name}
{g.original} · {fmtSize(g.size)}
)}
); }; /* ===== Upload modal ===== */ const GeoUploadModal = ({ open, onClose, onSubmit }) => { const [file, setFile] = useState(null); const [name, setName] = useState(""); const [desc, setDesc] = useState(""); const [dragging, setDragging] = useState(false); const [busy, setBusy] = useState(false); const inputRef = useRef(null); useEffect(() => { if (open) { setFile(null); setName(""); setDesc(""); setBusy(false); } }, [open]); useEffect(() => { if (!open) return; const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); if (!open) return null; const ALLOWED = ["cdb","iges","igs","step","stp","anf","x_t","x_b"]; const onFile = (f) => { if (!f) return; const ext = (f.name.split(".").pop() || "").toLowerCase(); if (!ALLOWED.includes(ext)) { alert("不支持的格式:." + ext + "\n支持:" + ALLOWED.map(e => "." + e).join(" · ")); return; } if (f.size > 100 * 1024 * 1024) { alert("文件超过 100 MB 上限"); return; } setFile(f); if (!name) setName(f.name.replace(/\.[^.]+$/, "")); }; const fmt = file ? (file.name.split(".").pop() || "").toLowerCase().replace("_", "") : null; const fmtKey = fmt === "xt" ? "xt" : fmt === "xb" ? "xb" : fmt; const submit = async () => { if (!file || !name.trim()) return; setBusy(true); try { const g = await uploadGeometry({ file, name: name.trim(), description: desc.trim() }); onSubmit(g); onClose(); } catch (e) { alert("上传失败:" + e.message); } finally { setBusy(false); } }; return (
{ if (e.target === e.currentTarget) onClose(); }}>
上传几何
{!file ? (
inputRef.current?.click()} onDragOver={(e) => { e.preventDefault(); setDragging(true); }} onDragLeave={() => setDragging(false)} onDrop={(e) => { e.preventDefault(); setDragging(false); onFile(e.dataTransfer.files?.[0]); }}>
拖入 CAD 文件,或点击选择
支持 .cdb · .step · .iges · .anf · .x_t · .x_b
单文件 ≤ 100 MB
onFile(e.target.files?.[0])}/>
) : (
{file.name}
{fmtSize(file.size)} · 自动识别为 {FMT_META[fmtKey]?.tag || fmt.toUpperCase()}
)}
名称 *
setName(e.target.value)} placeholder="例如:盘式制动器装配体"/>
描述(可选)