2026-03-12 11:24:10 +08:00
|
|
|
import { useState, useEffect, useRef } from 'react';
|
|
|
|
|
import { useRouter } from 'next/router';
|
|
|
|
|
import { isLoggedIn, exportData, importData, logout as apiLogout } from '../lib/api';
|
|
|
|
|
import AppShell from '../components/AppShell';
|
|
|
|
|
import { Icon } from '../components/icons';
|
|
|
|
|
import { useToast } from '../components/ToastProvider';
|
|
|
|
|
import { useConfirm } from '../components/ConfirmProvider';
|
|
|
|
|
|
|
|
|
|
export default function Settings() {
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const fileInputRef = useRef(null);
|
|
|
|
|
const { showToast } = useToast();
|
|
|
|
|
const { confirm } = useConfirm();
|
|
|
|
|
const [exporting, setExporting] = useState(false);
|
|
|
|
|
const [importing, setImporting] = useState(false);
|
|
|
|
|
const [loggingOut, setLoggingOut] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!isLoggedIn()) {
|
|
|
|
|
router.push('/login');
|
|
|
|
|
}
|
|
|
|
|
}, [router]);
|
|
|
|
|
|
|
|
|
|
const handleLogout = async () => {
|
|
|
|
|
const shouldLogout = await confirm({
|
|
|
|
|
title: '退出当前账号?',
|
|
|
|
|
description: '退出后需要重新登录才能继续使用。',
|
|
|
|
|
confirmText: '退出登录',
|
|
|
|
|
tone: 'info',
|
|
|
|
|
});
|
|
|
|
|
if (!shouldLogout) return;
|
|
|
|
|
|
|
|
|
|
setLoggingOut(true);
|
|
|
|
|
apiLogout();
|
|
|
|
|
showToast('已退出登录', { tone: 'success' });
|
|
|
|
|
router.push('/login');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleExport = async () => {
|
|
|
|
|
setExporting(true);
|
|
|
|
|
try {
|
|
|
|
|
const data = await exportData();
|
|
|
|
|
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
const link = document.createElement('a');
|
|
|
|
|
link.href = url;
|
|
|
|
|
link.download = `accountbook_backup_${new Date().toISOString().split('T')[0]}.json`;
|
|
|
|
|
link.click();
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
showToast('导出成功', { tone: 'success' });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
showToast(`导出失败: ${err.message}`, { tone: 'error' });
|
|
|
|
|
} finally {
|
|
|
|
|
setExporting(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleImport = async (e) => {
|
|
|
|
|
const file = e.target.files?.[0];
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
|
|
setImporting(true);
|
|
|
|
|
try {
|
|
|
|
|
const text = await file.text();
|
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
|
await importData(data);
|
|
|
|
|
showToast('导入成功', { tone: 'success' });
|
|
|
|
|
e.target.value = '';
|
|
|
|
|
} catch (err) {
|
|
|
|
|
showToast(`导入失败: ${err.message}`, { tone: 'error' });
|
|
|
|
|
} finally {
|
|
|
|
|
setImporting(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
2026-03-17 11:19:26 +08:00
|
|
|
<AppShell title="设置" subtitle="管理您的数据与账号" compactHeader>
|
|
|
|
|
<div className="settings-list">
|
|
|
|
|
<button type="button" className="settings-link" onClick={handleExport} disabled={exporting || importing}>
|
|
|
|
|
<Icon name="download" size={18} />
|
|
|
|
|
<span>导出数据</span>
|
|
|
|
|
<Icon name="chevronRight" size={16} className="chevron" />
|
|
|
|
|
</button>
|
|
|
|
|
<button type="button" className="settings-link" onClick={() => fileInputRef.current?.click()} disabled={exporting || importing}>
|
|
|
|
|
<Icon name="upload" size={18} />
|
|
|
|
|
<span>导入数据</span>
|
|
|
|
|
<Icon name="chevronRight" size={16} className="chevron" />
|
|
|
|
|
</button>
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
ref={fileInputRef}
|
|
|
|
|
onChange={handleImport}
|
|
|
|
|
accept=".json"
|
|
|
|
|
style={{ display: 'none' }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-03-12 11:24:10 +08:00
|
|
|
|
2026-03-17 11:19:26 +08:00
|
|
|
<div className="settings-list">
|
|
|
|
|
<button type="button" className="settings-link settings-link--danger" onClick={handleLogout} disabled={loggingOut}>
|
|
|
|
|
<Icon name="logout" size={18} />
|
|
|
|
|
<span>{loggingOut ? '退出中...' : '退出登录'}</span>
|
|
|
|
|
<Icon name="chevronRight" size={16} className="chevron" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-03-12 11:24:10 +08:00
|
|
|
|
2026-03-17 11:19:26 +08:00
|
|
|
<div className="settings-footer">
|
|
|
|
|
<p>简易记账本 v1.0</p>
|
|
|
|
|
</div>
|
2026-03-12 11:24:10 +08:00
|
|
|
</AppShell>
|
|
|
|
|
);
|
|
|
|
|
}
|