feat: 添加设置页面,包含通用、安全、数据和关于选项卡,并支持偏好设置和账户管理。
This commit is contained in:
@@ -5,8 +5,10 @@ import AppLockSettings from '../../components/settings/AppLockSettings';
|
|||||||
import { CapsuleSelector } from '../../components/common/CapsuleSelector/CapsuleSelector';
|
import { CapsuleSelector } from '../../components/common/CapsuleSelector/CapsuleSelector';
|
||||||
import { CustomSelect } from '../../components/common/CustomSelect/CustomSelect';
|
import { CustomSelect } from '../../components/common/CustomSelect/CustomSelect';
|
||||||
import { getSupportedCurrencies } from '../../services/exchangeRateService';
|
import { getSupportedCurrencies } from '../../services/exchangeRateService';
|
||||||
import { getSettings, updateSettings } from '../../services/settingsService';
|
import { getSupportedCurrencies } from '../../services/exchangeRateService';
|
||||||
import type { UserSettings } from '../../types';
|
import { getSettings, updateSettings, updateDefaultAccounts } from '../../services/settingsService';
|
||||||
|
import { getAccounts } from '../../services/accountService';
|
||||||
|
import type { UserSettings, Account } from '../../types';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { ThemePicker } from '../../components/settings/ThemePicker/ThemePicker';
|
import { ThemePicker } from '../../components/settings/ThemePicker/ThemePicker';
|
||||||
import './Settings.css';
|
import './Settings.css';
|
||||||
@@ -38,6 +40,7 @@ function Settings() {
|
|||||||
firstDayOfWeek: parseInt(localStorage.getItem('firstDayOfWeek') || '1'),
|
firstDayOfWeek: parseInt(localStorage.getItem('firstDayOfWeek') || '1'),
|
||||||
});
|
});
|
||||||
const [userSettings, setUserSettings] = useState<UserSettings | null>(null);
|
const [userSettings, setUserSettings] = useState<UserSettings | null>(null);
|
||||||
|
const [accounts, setAccounts] = useState<Account[]>([]);
|
||||||
const [saveMessage, setSaveMessage] = useState<string>('');
|
const [saveMessage, setSaveMessage] = useState<string>('');
|
||||||
|
|
||||||
const currencies = getSupportedCurrencies();
|
const currencies = getSupportedCurrencies();
|
||||||
@@ -45,6 +48,7 @@ function Settings() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load user settings
|
// Load user settings
|
||||||
loadUserSettings();
|
loadUserSettings();
|
||||||
|
loadAccounts();
|
||||||
// Sync settings state with global theme mode
|
// Sync settings state with global theme mode
|
||||||
setSettings(prev => ({ ...prev, theme: themeMode }));
|
setSettings(prev => ({ ...prev, theme: themeMode }));
|
||||||
}, [themeMode]);
|
}, [themeMode]);
|
||||||
@@ -58,6 +62,15 @@ function Settings() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadAccounts = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getAccounts();
|
||||||
|
setAccounts(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load accounts:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleUserSettingChange = async (key: keyof UserSettings, value: any) => {
|
const handleUserSettingChange = async (key: keyof UserSettings, value: any) => {
|
||||||
if (!userSettings) return;
|
if (!userSettings) return;
|
||||||
|
|
||||||
@@ -74,6 +87,38 @@ function Settings() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDefaultAccountChange = async (key: 'defaultExpenseAccountId' | 'defaultIncomeAccountId', value: number) => {
|
||||||
|
if (!userSettings) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updateData = {
|
||||||
|
[key]: value
|
||||||
|
};
|
||||||
|
await updateDefaultAccounts(updateData);
|
||||||
|
|
||||||
|
// Update local state
|
||||||
|
let updatedSettings = { ...userSettings };
|
||||||
|
if (key === 'defaultExpenseAccountId') {
|
||||||
|
updatedSettings.defaultExpenseAccountId = value;
|
||||||
|
// Update the related object for immediate UI feedback if needed
|
||||||
|
const account = accounts.find(a => a.id === value);
|
||||||
|
if (account) updatedSettings.defaultExpenseAccount = account;
|
||||||
|
} else {
|
||||||
|
updatedSettings.defaultIncomeAccountId = value;
|
||||||
|
const account = accounts.find(a => a.id === value);
|
||||||
|
if (account) updatedSettings.defaultIncomeAccount = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserSettings(updatedSettings);
|
||||||
|
setSaveMessage('默认账户已更新');
|
||||||
|
setTimeout(() => setSaveMessage(''), 2000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to update default account:', err);
|
||||||
|
setSaveMessage('更新失败');
|
||||||
|
setTimeout(() => setSaveMessage(''), 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -276,6 +321,48 @@ function Settings() {
|
|||||||
<div className="settings-group">
|
<div className="settings-group">
|
||||||
<h3>记账设置</h3>
|
<h3>记账设置</h3>
|
||||||
|
|
||||||
|
<div className="settings-item">
|
||||||
|
<div className="settings-item-info">
|
||||||
|
<label className="settings-item__label">默认支出账户</label>
|
||||||
|
<p className="settings-item__description">AI 记账和快捷记账时的默认扣款账户</p>
|
||||||
|
</div>
|
||||||
|
<CustomSelect
|
||||||
|
className="settings-custom-select"
|
||||||
|
value={userSettings?.defaultExpenseAccountId || ''}
|
||||||
|
onChange={(value) => handleDefaultAccountChange('defaultExpenseAccountId', Number(value))}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: '不设置' },
|
||||||
|
...accounts
|
||||||
|
.filter(a => a.type !== 'credit_card' && a.type !== 'credit_line' ? true : true) // Show all for now, maybe filter logic later
|
||||||
|
.map(account => ({
|
||||||
|
value: account.id,
|
||||||
|
label: account.name
|
||||||
|
}))
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-item">
|
||||||
|
<div className="settings-item-info">
|
||||||
|
<label className="settings-item__label">默认收入账户</label>
|
||||||
|
<p className="settings-item__description">记录收入时的默认入账账户</p>
|
||||||
|
</div>
|
||||||
|
<CustomSelect
|
||||||
|
className="settings-custom-select"
|
||||||
|
value={userSettings?.defaultIncomeAccountId || ''}
|
||||||
|
onChange={(value) => handleDefaultAccountChange('defaultIncomeAccountId', Number(value))}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: '不设置' },
|
||||||
|
...accounts
|
||||||
|
//.filter(a => a.accountType === 'asset') // Typically income goes to assets
|
||||||
|
.map(account => ({
|
||||||
|
value: account.id,
|
||||||
|
label: account.name
|
||||||
|
}))
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="settings-item">
|
<div className="settings-item">
|
||||||
<div className="settings-item-info">
|
<div className="settings-item-info">
|
||||||
<label className="settings-item__label">精确记账时间</label>
|
<label className="settings-item__label">精确记账时间</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user