Files
accountbook/frontend/pages/settings.js
T
DeveloperandClaude Opus 4.6 3347a256b2 Initial commit: accountbook project
- Node.js backend server
- Frontend application
- Backup script
- Project specification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:24:10 +08:00

154 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 (
<AppShell title="设置" subtitle="数据、账号与应用信息" compactHeader>
<section className="card settings-section settings-section--refresh">
<div className="section-heading section-heading--compact">
<div>
<p className="eyebrow">数据管理</p>
<h2>导入与导出</h2>
</div>
</div>
<div className="settings-item">
<div className="settings-info">
<div className="settings-title">导出数据</div>
<div className="settings-desc">将所有账目导出为 JSON 文件方便备份</div>
</div>
<button type="button" className="btn btn-primary btn-inline" onClick={handleExport} disabled={exporting || importing}>
<span className="button-content">
<Icon name="download" size={16} />
{exporting ? '导出中...' : '导出'}
</span>
</button>
</div>
<div className="settings-item">
<div className="settings-info">
<div className="settings-title">导入数据</div>
<div className="settings-desc"> JSON 文件恢复账目数据</div>
</div>
<button type="button" className="btn btn-secondary btn-inline" onClick={() => fileInputRef.current?.click()} disabled={exporting || importing}>
<span className="button-content">
<Icon name="upload" size={16} />
{importing ? '导入中...' : '导入'}
</span>
</button>
<input
type="file"
ref={fileInputRef}
onChange={handleImport}
accept=".json"
style={{ display: 'none' }}
/>
</div>
</section>
<section className="card settings-section settings-section--refresh">
<div className="section-heading section-heading--compact">
<div>
<p className="eyebrow">账号</p>
<h2>登录状态</h2>
</div>
</div>
<div className="settings-item settings-item--stacked">
<div className="settings-info">
<div className="settings-title">退出登录</div>
<div className="settings-desc">从当前设备安全退出并返回登录页</div>
</div>
<button type="button" className="btn btn-danger btn-inline" onClick={handleLogout} disabled={loggingOut}>
<span className="button-content">
<Icon name="logout" size={16} />
{loggingOut ? '退出中...' : '退出登录'}
</span>
</button>
</div>
</section>
<section className="card settings-section settings-section--refresh">
<div className="section-heading section-heading--compact">
<div>
<p className="eyebrow">关于</p>
<h2>应用信息</h2>
</div>
</div>
<div className="about-info">
<p>简易记账本 v1.0</p>
<p className="sub">基于 React + Next.js 开发</p>
</div>
</section>
</AppShell>
);
}