177 lines
5.9 KiB
TypeScript
177 lines
5.9 KiB
TypeScript
"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>
|
||
)
|
||
}
|