Files
mnote/wolai-frontend/src/app/(app)/wolai-import/page.tsx
T
2026-01-21 18:21:10 +08:00

152 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useMemo, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
export default function WolaiImportPage() {
const router = useRouter();
const searchParams = useSearchParams();
const parentId = useMemo(() => searchParams.get("parentId") ?? "", [searchParams]);
const [file, setFile] = useState<File | null>(null);
const [zipPath, setZipPath] = useState("");
const [rootMdPath, setRootMdPath] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string>("");
const [warnings, setWarnings] = useState<string[]>([]);
const handleSubmit = async () => {
if (!zipPath.trim() && !file) {
setError("请填写本地 ZIP 路径,或选择 ZIP 文件");
return;
}
setBusy(true);
setError("");
setWarnings([]);
try {
const useLocalPath = Boolean(zipPath.trim());
let res: Response;
if (useLocalPath) {
res = await fetch("/api/wolai-import", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
zipPath: zipPath.trim(),
parentId: parentId || undefined,
rootMdPath: rootMdPath.trim() || undefined,
}),
});
} else {
if (!file) {
setError("请选择 ZIP 文件");
return;
}
const formData = new FormData();
formData.set("file", file);
if (parentId) formData.set("parentId", parentId);
if (rootMdPath.trim()) formData.set("rootMdPath", rootMdPath.trim());
res = await fetch("/api/wolai-import", { method: "POST", body: formData });
}
const payload = (await res.json().catch(() => null)) as any;
if (!res.ok) {
const msg = payload?.error ? String(payload.error) : `导入失败:${res.status}`;
setError(msg);
if (res.status === 409 && Array.isArray(payload?.candidates)) {
setWarnings([`无法自动判断入口页面,请在“入口 rootMdPath”填入其中一个:`, ...payload.candidates.map(String)]);
}
return;
}
const ws = Array.isArray(payload?.warnings) ? payload.warnings.map(String) : [];
setWarnings(ws);
const rootDocumentId = String(payload?.rootDocumentId ?? "");
if (rootDocumentId) {
router.push(`/documents/${encodeURIComponent(rootDocumentId)}`);
return;
}
setError("导入完成但缺少 rootDocumentId");
} catch (err) {
const msg = err instanceof Error ? err.message : "导入失败";
setError(msg);
} finally {
setBusy(false);
}
};
return (
<div className="mx-auto w-full max-w-2xl p-6">
<Card>
<CardHeader>
<CardTitle>导入 WolaiMarkdown ZIP</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<div className="text-sm font-medium">本地 ZIP 路径(大文件推荐)</div>
<Input
value={zipPath}
onChange={(e) => setZipPath(e.target.value)}
placeholder="例如:C:\\Users\\liaib\\Downloads\\个人.zip"
disabled={busy}
/>
<div className="text-xs text-muted-foreground">
说明:你的 <span className="font-mono">个人.zip</span> 1.26GB,浏览器上传很容易 500
建议填本地路径并清空 ZIP 文件选择(或不选择文件)。
</div>
</div>
<div className="space-y-2">
<div className="text-sm font-medium">ZIP 文件</div>
<Input
type="file"
accept=".zip,application/zip"
onChange={(e) => setFile(e.target.files?.[0] ?? null)}
disabled={busy}
/>
<div className="text-xs text-muted-foreground">说明:请使用 Wolai 导出的“Markdown(含附件/图片)”ZIP</div>
</div>
<div className="space-y-2">
<div className="text-sm font-medium">入口 rootMdPath(可选)</div>
<Input
value={rootMdPath}
onChange={(e) => setRootMdPath(e.target.value)}
placeholder="例如:dQeAax/个人.md(留空则自动判断)"
disabled={busy}
/>
<div className="text-xs text-muted-foreground">
自动判断失败时会返回候选列表,你可以把其中一个填进来重试。
</div>
</div>
{parentId ? (
<div className="text-xs text-muted-foreground">
将作为子页面导入到:<span className="font-mono">{parentId}</span>
</div>
) : null}
{error ? <div className="text-sm text-red-600">{error}</div> : null}
{warnings.length > 0 ? (
<div className={cn("rounded-md border bg-muted/30 p-3 text-xs whitespace-pre-wrap")}>
{warnings.join("\n")}
</div>
) : null}
<div className="flex gap-2">
<Button onClick={() => void handleSubmit()} disabled={busy || (!zipPath.trim() && !file)}>
{busy ? "导入中..." : "开始导入"}
</Button>
<Button variant="secondary" onClick={() => router.back()} disabled={busy}>
返回
</Button>
</div>
</CardContent>
</Card>
</div>
);
}