This commit is contained in:
freedakgmail
2025-11-23 03:07:39 +08:00
parent 626d7dddde
commit e92884ae53
679 changed files with 208175 additions and 1183474 deletions
+176
View File
@@ -0,0 +1,176 @@
"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>
)
}