"use client"; import { useRef, useState } from "react"; import { ArrowLeft, Download, FileUp, ShieldCheck } from "lucide-react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; type Backup = { format: "cet6-checkin-backup"; version: 1; exportedAt: string; data: { progress: unknown[]; studyDays: unknown[]; mockExams: unknown[] } }; export default function BackupClient() { const input = useRef(null), [pending, setPending] = useState(null), [status, setStatus] = useState("导出文件可留作换设备或恢复数据时使用。"), [working, setWorking] = useState(false); const exportAll = async () => { setWorking(true); setStatus("正在生成完整备份…"); try { const response = await fetch("/api/backup"), backup = await response.json(); if (!response.ok) throw Error(backup.error); const blob = new Blob([JSON.stringify(backup, null, 2)], { type: "application/json" }), url = URL.createObjectURL(blob), link = document.createElement("a"); link.href = url; link.download = `cet6-checkin-backup-${backup.exportedAt.slice(0, 10)}.json`; link.click(); URL.revokeObjectURL(url); setStatus(`备份已下载:含 ${backup.data.progress.length} 条打卡、${backup.data.studyDays.length} 条学习记录和 ${backup.data.mockExams.length} 条模考记录。`); } catch (error) { setStatus(error instanceof Error ? error.message : "备份生成失败,请稍后重试。"); } finally { setWorking(false); } }; const selectFile = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; event.target.value = ""; if (!file) return; if (file.size > 2 * 1024 * 1024) return setStatus("备份文件过大,请选择 2 MB 以内的 JSON 文件。"); try { const backup = JSON.parse(await file.text()) as Backup; if (backup.format !== "cet6-checkin-backup" || backup.version !== 1 || !backup.data || !Array.isArray(backup.data.progress) || !Array.isArray(backup.data.studyDays) || !Array.isArray(backup.data.mockExams)) throw Error("这不是可识别的六级打卡备份文件。"); setPending(backup); } catch (error) { setStatus(error instanceof Error ? error.message : "无法读取该文件。"); } }; const importAll = async () => { if (!pending) return; setWorking(true); try { const response = await fetch("/api/backup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(pending) }), result = await response.json(); if (!response.ok) throw Error(result.error); setStatus(`导入完成:${result.imported.progress} 条打卡、${result.imported.studyDays} 条学习记录、${result.imported.mockExams} 条模考记录。`); setPending(null); } catch (error) { setStatus(error instanceof Error ? error.message : "导入失败,请稍后重试。"); } finally { setWorking(false); } }; const counts = pending ? `${pending.data.progress.length} 条打卡、${pending.data.studyDays.length} 条学习记录、${pending.data.mockExams.length} 条模考记录` : ""; return

DATA BACKUP

数据备份与迁移

一个 JSON 文件保存所有云端学习数据。

返回打卡

{status}

EXPORT

导出完整备份

下载一个 JSON 文件,包含每日打卡、学习用时、周复盘和模考记录。

IMPORT

从备份恢复

选择此前导出的 JSON 文件。相同日期的数据会由备份覆盖,其他已有记录会保留。

void selectFile(event)} />

导入前会先检查文件格式。 备份文件仅含你的学习记录;请妥善保管,不要上传到公开位置。

!open && setPending(null)}>确认导入这份备份?将导入 {counts}。相同日期的记录会被这份备份覆盖,操作完成后建议刷新其他已打开的页面。取消 void importAll()}>确认导入
; }