feat: 新增储蓄罐存取款模态框,支持金额输入、快速选择、进度展示及账户余额校验。

This commit is contained in:
2026-02-01 19:50:26 +08:00
parent e3b40259f4
commit b14f6606f2
4 changed files with 36 additions and 9 deletions

27
package-lock.json generated
View File

@@ -21,6 +21,7 @@
"react": "^19.2.0", "react": "^19.2.0",
"react-confetti": "^6.4.0", "react-confetti": "^6.4.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^7.12.0", "react-router-dom": "^7.12.0",
@@ -3938,6 +3939,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/goober": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
"integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==",
"license": "MIT",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -5867,6 +5877,23 @@
"react": "^19.2.3" "react": "^19.2.3"
} }
}, },
"node_modules/react-hot-toast": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
"integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
"license": "MIT",
"dependencies": {
"csstype": "^3.1.3",
"goober": "^2.1.16"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-icons": { "node_modules/react-icons": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",

View File

@@ -29,6 +29,7 @@
"react": "^19.2.0", "react": "^19.2.0",
"react-confetti": "^6.4.0", "react-confetti": "^6.4.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^7.12.0", "react-router-dom": "^7.12.0",

View File

@@ -4,6 +4,7 @@ import { router } from './router';
import { ThemeProvider, PrivacyProvider, NotificationProvider, GuideProvider, useAutoTokenRefresh } from './hooks'; import { ThemeProvider, PrivacyProvider, NotificationProvider, GuideProvider, useAutoTokenRefresh } from './hooks';
import { SettingsProvider } from './contexts/SettingsContext'; import { SettingsProvider } from './contexts/SettingsContext';
import { Toaster } from 'react-hot-toast';
/** /**
* App Content Component * App Content Component
@@ -28,6 +29,7 @@ function App() {
<GuideProvider> <GuideProvider>
<SettingsProvider> <SettingsProvider>
<AppContent /> <AppContent />
<Toaster position="top-center" reverseOrder={false} />
</SettingsProvider> </SettingsProvider>
</GuideProvider> </GuideProvider>
</NotificationProvider> </NotificationProvider>

View File

@@ -4,6 +4,7 @@
*/ */
import React, { useState } from 'react'; import React, { useState } from 'react';
import toast from 'react-hot-toast';
import type { PiggyBank, Account } from '../../../types'; import type { PiggyBank, Account } from '../../../types';
import { formatCurrency } from '../../../utils/format'; import { formatCurrency } from '../../../utils/format';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
@@ -28,7 +29,6 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
}) => { }) => {
const [amount, setAmount] = useState<string>(''); const [amount, setAmount] = useState<string>('');
const [note, setNote] = useState<string>(''); const [note, setNote] = useState<string>('');
const [error, setError] = useState<string>('');
const isDeposit = type === 'deposit'; const isDeposit = type === 'deposit';
const title = isDeposit ? '存入' : '取出'; const title = isDeposit ? '存入' : '取出';
@@ -38,7 +38,6 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value; const value = e.target.value;
setAmount(value); setAmount(value);
setError('');
}; };
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
@@ -48,17 +47,17 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
// Validation // Validation
if (!amount || isNaN(numAmount) || numAmount <= 0) { if (!amount || isNaN(numAmount) || numAmount <= 0) {
setError('请输入有效的金额'); toast.error('请输入有效的金额');
return; return;
} }
if (!isDeposit && numAmount > piggyBank.currentAmount) { if (!isDeposit && numAmount > piggyBank.currentAmount) {
setError('取出金额不能超过当前余额'); toast.error('取出金额不能超过当前余额');
return; return;
} }
if (isDeposit && linkedAccount && numAmount > linkedAccount.balance) { if (isDeposit && linkedAccount && numAmount > linkedAccount.balance) {
setError(`账户余额不足 (可用: ${formatCurrency(linkedAccount.balance, linkedAccount.currency)})`); toast.error(`账户余额不足 (可用: ${formatCurrency(linkedAccount.balance, linkedAccount.currency)})`);
return; return;
} }
@@ -67,7 +66,6 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
const handleQuickAmount = (value: number) => { const handleQuickAmount = (value: number) => {
setAmount(value.toString()); setAmount(value.toString());
setError('');
}; };
// Quick amount buttons // Quick amount buttons
@@ -163,8 +161,7 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
id="amount" id="amount"
value={amount} value={amount}
onChange={handleAmountChange} onChange={handleAmountChange}
className={`piggy-bank-transaction-modal__input ${error ? 'piggy-bank-transaction-modal__input--error' : '' className="piggy-bank-transaction-modal__input"
}`}
placeholder="0.00" placeholder="0.00"
step="0.01" step="0.01"
min="0" min="0"
@@ -172,7 +169,7 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
disabled={isLoading} disabled={isLoading}
autoFocus autoFocus
/> />
{error && <span className="piggy-bank-transaction-modal__error">{error}</span>} {/* Error handled by toast */}
</div> </div>
{linkedAccount ? ( {linkedAccount ? (