From 1df2950097d06a0676cfe8670b7898e09853a3c0 Mon Sep 17 00:00:00 2001 From: kennethcheng Date: Sun, 12 Apr 2026 06:27:36 +0800 Subject: [PATCH] v1.0.2 (2026-04-12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 🗑️ 删除交易记录功能:支持删除误添加的交易记录,自动回滚账户余额和持仓变化 - 💰 显示货币选择器:支持 CNY/USD/HKD 三种货币显示,默认 CNY - 🔄 实时货币转换:根据汇率自动转换总资产显示 --- README.md | 9 +++ src/app/api/transactions/route.ts | 84 +++++++++++++++++++++ src/app/page.tsx | 121 ++++++++++++++++++++++++++++-- src/lib/api.ts | 8 ++ 4 files changed, 215 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 06e5ec1..63f79a9 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,9 @@ npx prisma db seed # 5. 启动开发服务器 npm run dev + +# 6. 重置数据库 +npx prisma migrate reset --force ``` ### 环境变量 @@ -382,6 +385,12 @@ MIT License - 详见 [LICENSE](LICENSE) 文件 ## 更新日志 +### v1.0.2 (2026-04-12) + +- 🗑️ 删除交易记录功能:支持删除误添加的交易记录,自动回滚账户余额和持仓变化 +- 💰 显示货币选择器:支持 CNY/USD/HKD 三种货币显示,默认 CNY +- 🔄 实时货币转换:根据汇率自动转换总资产显示 + ### v1.0.1 (2026-04-12) - 🔧 账户名称优化:改为港股账户、美股账户、A股账户、加密货币账户 diff --git a/src/app/api/transactions/route.ts b/src/app/api/transactions/route.ts index 213d99d..707dc7b 100644 --- a/src/app/api/transactions/route.ts +++ b/src/app/api/transactions/route.ts @@ -134,3 +134,87 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Failed to create transaction' }, { status: 500 }) } } + +// 删除交易记录 +export async function DELETE(request: Request) { + try { + const { searchParams } = new URL(request.url) + const id = searchParams.get('id') + + if (!id) { + return NextResponse.json({ error: 'Transaction ID is required' }, { status: 400 }) + } + + // 获取交易记录 + const transaction = await prisma.transaction.findUnique({ + where: { id }, + }) + + if (!transaction) { + return NextResponse.json({ error: 'Transaction not found' }, { status: 404 }) + } + + // 开启事务处理回滚 + await prisma.$transaction(async (tx) => { + // 如果是入金/出金,反向更新账户余额 + if (transaction.type === 'DEPOSIT' || transaction.type === 'WITHDRAW') { + const balanceChange = transaction.type === 'DEPOSIT' + ? new Prisma.Decimal(transaction.amount).negated() + : new Prisma.Decimal(transaction.amount) + await tx.account.update({ + where: { id: transaction.accountId }, + data: { balance: { increment: balanceChange } }, + }) + } + + // 如果是买入/卖出,反向更新持仓 + if ((transaction.type === 'BUY' || transaction.type === 'SELL') && transaction.symbol && transaction.quantity && transaction.price) { + const position = await tx.position.findUnique({ + where: { accountId_symbol: { accountId: transaction.accountId, symbol: transaction.symbol } }, + }) + + if (position) { + if (transaction.type === 'BUY') { + // 撤销买入:减少持仓数量 + const newQty = position.quantity.minus(new Prisma.Decimal(transaction.quantity)) + if (newQty.lte(0)) { + // 持仓全部撤销,删除记录 + await tx.position.delete({ + where: { accountId_symbol: { accountId: transaction.accountId, symbol: transaction.symbol } }, + }) + } else { + // 按比例撤销平均成本 + const undoCost = new Prisma.Decimal(transaction.quantity).times(new Prisma.Decimal(transaction.price)) + const remainingCost = position.averageCost.times(position.quantity).minus(undoCost) + const newAvgCost = remainingCost.div(newQty) + await tx.position.update({ + where: { accountId_symbol: { accountId: transaction.accountId, symbol: transaction.symbol } }, + data: { + quantity: newQty, + averageCost: newAvgCost, + }, + }) + } + } else if (transaction.type === 'SELL') { + // 撤销卖出:恢复持仓数量 + const newQty = position.quantity.plus(new Prisma.Decimal(transaction.quantity)) + await tx.position.update({ + where: { accountId_symbol: { accountId: transaction.accountId, symbol: transaction.symbol } }, + data: { quantity: newQty }, + }) + } + } + } + + // 删除交易记录 + await tx.transaction.delete({ + where: { id }, + }) + }) + + return NextResponse.json({ success: true }) + } catch (error) { + console.error('Delete transaction error:', error) + return NextResponse.json({ error: 'Failed to delete transaction' }, { status: 500 }) + } +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 0a11afa..11674ca 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -21,7 +21,7 @@ import { } from 'lucide-react' import { fetchAccounts, fetchTransactions, fetchPositions, - fetchSecurities, createTransaction, formatCurrency, formatPercent, + fetchSecurities, createTransaction, deleteTransaction, formatCurrency, formatPercent, marketLabels, transactionTypeLabels } from '@/lib/api' import { Account, Position, Transaction, MarketType, TransactionType, Security } from '@/types' @@ -77,6 +77,20 @@ interface AnalyticsSummary { positionCount: number } +// 货币转换函数(以 USD 为基准) +const EXCHANGE_RATES: Record = { + USD: 1, + CNY: 7.24, // 1 USD = 7.24 CNY + HKD: 7.78, // 1 USD = 7.78 HKD +} + +function convertCurrency(amount: number, from: string, to: string): number { + if (from === to) return amount + // 先转换为 USD,再转换为目标货币 + const usdAmount = amount / EXCHANGE_RATES[from] + return usdAmount * EXCHANGE_RATES[to] +} + // 主页面组件 export default function Dashboard() { // 状态定义 @@ -97,6 +111,13 @@ export default function Dashboard() { const [importFile, setImportFile] = useState(null) const [importData, setImportData] = useState([]) + // 显示货币状态(默认 CNY) + const [displayCurrency, setDisplayCurrency] = useState<'CNY' | 'USD' | 'HKD'>('CNY') + + // 交易记录删除状态 + const [transactionToDelete, setTransactionToDelete] = useState(null) + const [showDeleteTxDialog, setShowDeleteTxDialog] = useState(false) + // 交易表单状态 const [txForm, setTxForm] = useState({ type: 'BUY' as TransactionType, @@ -253,6 +274,27 @@ export default function Dashboard() { setShowDeleteDialog(true) } + // 打开删除交易记录对话框 + const openDeleteTxDialog = (tx: Transaction) => { + setTransactionToDelete(tx) + setShowDeleteTxDialog(true) + } + + // 删除交易记录 + const handleDeleteTransaction = async () => { + if (!transactionToDelete) return + + try { + await deleteTransaction(transactionToDelete.id) + toast.success('交易记录已删除') + setShowDeleteTxDialog(false) + setTransactionToDelete(null) + loadData() + } catch (error) { + toast.error('删除交易记录失败') + } + } + // 重置交易表单 const resetTxForm = () => { setTxForm({ @@ -412,6 +454,17 @@ export default function Dashboard() {

投资持仓管理

+ {/* 显示货币选择 */} + {/* 账户选择下拉框 */}