feat:前端

This commit is contained in:
2026-09-14 11:54:20 +08:00
parent 86b13c6484
commit e2e1d73b3d
46 changed files with 4477 additions and 128 deletions
+16
View File
@@ -0,0 +1,16 @@
"use client";
import Link from "next/link";
import { ArrowLeft, Trash2, Upload } from "lucide-react";
import { useEffect, useState } from "react";
import { apiFetch } from "@/lib/api";
interface DocumentItem { id?: string; doc_id?: string; title?: string; filename?: string; status?: string; }
export default function KnowledgePage() {
const [items, setItems] = useState<DocumentItem[]>([]); const [file, setFile] = useState<File | null>(null); const [message, setMessage] = useState(""); const [isAdmin, setIsAdmin] = useState(false);
async function load() { try { setItems(await apiFetch<DocumentItem[]>("/knowledge/documents")); } catch (error) { setMessage(error instanceof Error ? error.message : "知识库加载失败"); } }
useEffect(() => { void apiFetch<{ user: { user_type?: string } }>("/auth/me").then(({ user }) => { const allowed = user.user_type === "ADMIN"; setIsAdmin(allowed); if (allowed) void load(); else setMessage("知识库管理仅对 ADMIN 开放"); }).catch(() => setMessage("请先登录管理员账号")); }, []);
async function upload(path: "/knowledge/documents/preview" | "/knowledge/documents/confirm") { if (!file) return; const body = new FormData(); body.append("file", file); body.append("title", file.name); body.append("strategy", "default"); const token = localStorage.getItem("huaxia_token"); const response = await fetch(`/backend${path}`, { method: "POST", headers: token ? { Authorization: `Bearer ${token}` } : undefined, body }); const payload = await response.json(); setMessage(payload.message ?? (response.ok ? "操作成功" : "操作失败")); if (response.ok && path.endsWith("confirm")) await load(); }
async function remove(id: string) { await apiFetch(`/knowledge/documents/${id}`, { method: "DELETE" }); await load(); }
return <main className="min-h-screen bg-slate-50 px-10 py-10"><div className="mx-auto max-w-6xl"><Link href="/admin/common" className="inline-flex items-center gap-2 text-sm text-slate-500"><ArrowLeft className="h-4 w-4" />返回员工工作区</Link><h1 className="mt-8 text-3xl font-semibold text-[var(--brand-navy)]">知识库管理</h1>{!isAdmin ? <p className="mt-6 rounded-xl border border-amber-200 bg-amber-50 p-5 text-sm text-amber-800">知识库管理仅对 ADMIN 开放。</p> : <><div className="mt-6 rounded-xl border border-slate-200 bg-white p-6"><input type="file" onChange={(event) => setFile(event.target.files?.[0] ?? null)} /><div className="mt-4 flex gap-3"><button onClick={() => void upload("/knowledge/documents/preview")} className="flex items-center gap-2 rounded-lg border px-4 py-2 text-sm"><Upload className="h-4 w-4" />预览</button><button onClick={() => void upload("/knowledge/documents/confirm")} className="rounded-lg bg-[var(--brand-primary)] px-4 py-2 text-sm text-white">确认入库</button></div>{message && <p className="mt-3 text-sm text-slate-500">{message}</p>}</div><div className="mt-6 divide-y divide-slate-100 rounded-xl border border-slate-200 bg-white">{items.map((item) => { const id = item.id ?? item.doc_id ?? ""; return <div key={id} className="flex items-center justify-between p-5 text-sm"><span>{item.title ?? item.filename ?? id} <small className="ml-3 text-slate-400">{item.status ?? "已入库"}</small></span><button onClick={() => void remove(id)} aria-label="删除文档" className="text-rose-500"><Trash2 className="h-4 w-4" /></button></div>; })}{!items.length && <p className="p-8 text-sm text-slate-400">暂无知识库文档</p>}</div></>}</div></main>;
}