'use client'; import { FormEvent, useEffect, useState } from 'react'; import { Cloud, Download, FileJson, LogOut, RefreshCw, Trash2, UploadCloud } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { AssessmentRecord } from '@/lib/assessment-types'; import { AnonymousLoginResult, AnonymousSession, clearAnonymousSession, deleteAnonymousProfile, getAnonymousSession, loginAnonymousProfile, syncAnonymousRecord, } from '@/lib/anonymous-client'; import { downloadText, profileToMarkdown } from '@/lib/assessment-export'; interface AnonymousSyncPanelProps { localRecords: AssessmentRecord[]; } function displayDate(value: string) { return new Date(value).toLocaleString('zh-CN', { hour12: false }); } export function AnonymousSyncPanel({ localRecords }: AnonymousSyncPanelProps) { const [codeName, setCodeName] = useState(''); const [password, setPassword] = useState(''); const [session, setSession] = useState(null); const [remote, setRemote] = useState(null); const [busy, setBusy] = useState(false); const login = async (nextSession: AnonymousSession) => { setBusy(true); try { const result = await loginAnonymousProfile(nextSession); setSession(nextSession); setRemote(result); setCodeName(nextSession.codeName); setPassword(''); toast.success('已进入匿名加密档案'); } catch (error) { toast.error(error instanceof Error ? error.message : '匿名档案登录失败'); } finally { setBusy(false); } }; useEffect(() => { const saved = getAnonymousSession(); if (!saved) return; setSession(saved); setCodeName(saved.codeName); void login(saved); }, []); const submit = (event: FormEvent) => { event.preventDefault(); void login({ codeName: codeName.trim(), password }); }; const logout = () => { clearAnonymousSession(); setSession(null); setRemote(null); setPassword(''); toast.success('已退出匿名档案'); }; const syncAll = async () => { if (!session || !localRecords.length) return; setBusy(true); try { await Promise.all(localRecords.map((record) => syncAnonymousRecord(record, session))); const result = await loginAnonymousProfile(session); setRemote(result); toast.success('当前本地记录已加密同步'); } catch (error) { toast.error(error instanceof Error ? error.message : '同步失败'); } finally { setBusy(false); } }; const removeRemoteProfile = async () => { if (!session) return; if (!window.confirm('删除这个匿名档案在服务器上的全部密文记录?本地记录不会删除。')) return; setBusy(true); try { await deleteAnonymousProfile(session); setSession(null); setRemote(null); toast.success('服务器匿名档案已删除'); } catch (error) { toast.error(error instanceof Error ? error.message : '删除失败'); } finally { setBusy(false); } }; const downloadRemoteMarkdown = () => { if (!remote) return; const profile = { id: remote.profile.id, name: remote.profile.codeName, createdAt: remote.profile.createdAt, updatedAt: remote.profile.updatedAt, }; downloadText(`${remote.profile.codeName}-服务器测评档案.md`, profileToMarkdown(profile, remote.records), 'text/markdown;charset=utf-8'); }; const downloadRemoteJson = () => { if (!remote) return; downloadText( `${remote.profile.codeName}-mindscope-remote.json`, JSON.stringify({ format: 'mindscope-anonymous-export', version: 1, exportedAt: new Date().toISOString(), profile: remote.profile, records: remote.records }, null, 2), 'application/json;charset=utf-8', ); }; return (
匿名加密档案

可选同步到服务器。只使用代号和恢复口令,不需要真实姓名、邮箱或手机号。 记录会先在浏览器中加密,服务器只保存密文。

{!session ? (
setCodeName(event.target.value)} placeholder="代号,不填真实姓名" autoComplete="username" /> setPassword(event.target.value)} placeholder="恢复口令" type="password" autoComplete="current-password" />
) : (
{remote?.profile.codeName || session.codeName}
服务器密文记录 {remote?.records.length || 0} 条 {remote?.profile.lastSeenAt ? ` · 最近进入 ${displayDate(remote.profile.lastSeenAt)}` : ''}
{!!remote?.records.length && (
{remote.records.slice(0, 8).map((record) => (
{record.questionnaireTitle} {displayDate(record.completedAt)}
))}
)}
)}
); }