Files
chinese-family-tree-2/app/migrate/page.tsx
T
freedakgmail e92884ae53 0.0.1.0
2025-11-23 03:07:39 +08:00

177 lines
5.9 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 { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Progress } from "@/components/ui/progress"
import { SiteHeader } from "@/components/site-header"
import { Database, AlertTriangle, CheckCircle2 } from "lucide-react"
import { migrateFromIndexedDB, clearIndexedDB } from "@/lib/migrate-data"
export default function MigratePage() {
const router = useRouter()
const [status, setStatus] = useState<"idle" | "migrating" | "success" | "error">("idle")
const [progress, setProgress] = useState(0)
const [result, setResult] = useState<any>(null)
const [error, setError] = useState("")
const handleMigrate = async () => {
setStatus("migrating")
setProgress(0)
setError("")
try {
// 模拟进度
setProgress(20)
// 这里需要先创建一个家族树,然后获取 treeId 和 userId
// 为了演示,我们先创建一个默认的家族树
const treeResponse = await fetch("/api/trees", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "我的家族树",
description: "从 IndexedDB 迁移的数据"
})
})
if (!treeResponse.ok) {
throw new Error("创建家族树失败")
}
const { tree } = await treeResponse.json()
setProgress(40)
// 执行迁移
const migrateResult = await migrateFromIndexedDB(tree.id, tree.ownerId)
setProgress(80)
setResult(migrateResult)
setProgress(100)
setStatus("success")
} catch (err: any) {
setError(err.message || "迁移失败")
setStatus("error")
}
}
const handleClearIndexedDB = async () => {
if (!confirm("确定要清空 IndexedDB 数据吗?此操作不可撤销!")) {
return
}
try {
await clearIndexedDB()
alert("IndexedDB 数据已清空")
} catch (err: any) {
alert("清空失败: " + err.message)
}
}
return (
<div className="min-h-screen bg-background flex flex-col">
<SiteHeader />
<main className="container mx-auto py-8 px-4 md:px-6 flex-1 max-w-3xl">
<h1 className="text-3xl font-serif font-bold mb-8"></h1>
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
IndexedDB PostgreSQL
</CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Alert>
<AlertTriangle className="h-4 w-4" />
<AlertTitle></AlertTitle>
<AlertDescription>
<ul className="list-disc list-inside mt-2 space-y-1">
<li></li>
<li></li>
<li></li>
</ul>
</AlertDescription>
</Alert>
{status === "migrating" && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span></span>
<span>{progress}%</span>
</div>
<Progress value={progress} />
</div>
)}
{status === "success" && result && (
<Alert className="border-green-500 bg-green-50">
<CheckCircle2 className="h-4 w-4 text-green-600" />
<AlertTitle className="text-green-900"></AlertTitle>
<AlertDescription className="text-green-800">
{result.count} / {result.total}
{result.errors > 0 && `,失败 ${result.errors}`}
</AlertDescription>
</Alert>
)}
{status === "error" && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle></AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="flex gap-4">
<Button
onClick={handleMigrate}
disabled={status === "migrating"}
className="flex-1"
>
{status === "migrating" ? "迁移中..." : "开始迁移"}
</Button>
{status === "success" && (
<Button
variant="outline"
onClick={() => router.push("/")}
>
</Button>
)}
</div>
</CardContent>
</Card>
<Card className="border-destructive/50">
<CardHeader>
<CardTitle className="text-destructive"></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<Button
variant="destructive"
onClick={handleClearIndexedDB}
disabled={status !== "success"}
>
IndexedDB
</Button>
</CardContent>
</Card>
</div>
</main>
</div>
)
}