From 44a1023cdda05f59f8e26c1e505994ec73e1afe2 Mon Sep 17 00:00:00 2001 From: hailin Date: Sun, 4 Jan 2026 23:45:17 -0800 Subject: [PATCH] feat(admin-web): add ledger detail display for system accounts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add getAllLedger API method in systemAccountReportService - Add LedgerEntryDTO, FixedAccountLedger, RegionAccountLedger types - Add ALL_LEDGER endpoint - Update SystemAccountsTab with ledger detail tab - Add expandable card UI for viewing account ledger entries - Add styles for ledger cards and tables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../SystemAccountsTab.module.scss | 123 +++++++++ .../SystemAccountsTab.tsx | 245 +++++++++++++++++- .../src/infrastructure/api/endpoints.ts | 2 + .../services/systemAccountReportService.ts | 9 + .../src/types/system-account.types.ts | 65 +++++ 5 files changed, 443 insertions(+), 1 deletion(-) diff --git a/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.module.scss b/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.module.scss index 96c8703f..9f1ec2fd 100644 --- a/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.module.scss +++ b/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.module.scss @@ -285,3 +285,126 @@ padding-top: 16px; border-top: 1px solid #e5e7eb; } + +/* [2026-01-05] 新增:分类账明细样式 */ +.sectionHeader { + display: flex; + justify-content: space-between; + align-items: center; +} + +.refreshButton { + padding: 6px 12px; + background-color: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; + border-radius: 6px; + cursor: pointer; + font-size: 13px; + transition: all 0.2s ease; + + &:hover { + background-color: #e5e7eb; + } +} + +.ledgerGroup { + margin-top: 16px; +} + +.ledgerCard { + background-color: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 8px; + margin-bottom: 12px; + overflow: hidden; +} + +.ledgerCardHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px 16px; + cursor: pointer; + background-color: #fff; + border-bottom: 1px solid #e5e7eb; + transition: background-color 0.2s ease; + + &:hover { + background-color: #f9fafb; + } +} + +.ledgerCardTitle { + display: flex; + align-items: center; + gap: 12px; + + .accountName { + font-size: 14px; + font-weight: 600; + color: #1f2937; + } + + .accountSequence { + font-size: 12px; + color: #9ca3af; + font-family: monospace; + } +} + +.ledgerCardInfo { + display: flex; + align-items: center; + gap: 12px; +} + +.ledgerCount { + font-size: 13px; + color: #6b7280; +} + +.expandIcon { + font-size: 12px; + color: #9ca3af; +} + +.ledgerCardBody { + padding: 16px; + background-color: #fff; +} + +.emptyLedger { + text-align: center; + padding: 24px; + color: #9ca3af; + font-size: 14px; +} + +.entryTypeBadge { + display: inline-block; + padding: 2px 8px; + font-size: 12px; + border-radius: 4px; + background-color: #e5e7eb; + color: #4b5563; +} + +.amountPositive { + color: #059669; + font-weight: 500; +} + +.amountNegative { + color: #dc2626; + font-weight: 500; +} + +.memo { + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 13px; + color: #6b7280; +} diff --git a/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.tsx b/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.tsx index 6168e925..beb27228 100644 --- a/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.tsx +++ b/frontend/admin-web/src/components/features/system-account-report/SystemAccountsTab.tsx @@ -1,6 +1,7 @@ /** * 系统账户报表Tab组件 * [2026-01-04] 新增:显示系统账户统计数据 + * [2026-01-05] 更新:添加分类账明细显示 * 回滚方式:删除此文件及整个 system-account-report 目录 */ 'use client'; @@ -10,7 +11,12 @@ import { systemAccountReportService } from '@/services/systemAccountReportServic import type { SystemAccountReportResponse, RegionAccountsSummary, + AllSystemAccountsLedgerResponse, + FixedAccountLedger, + RegionAccountLedger, + LedgerEntryDTO, } from '@/types'; +import { ENTRY_TYPE_LABELS, ACCOUNT_TYPE_LABELS } from '@/types'; import styles from './SystemAccountsTab.module.scss'; /** @@ -46,7 +52,9 @@ export default function SystemAccountsTab() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [reportData, setReportData] = useState(null); - const [activeTab, setActiveTab] = useState<'fixed' | 'province' | 'city' | 'settlement' | 'expired'>('fixed'); + const [ledgerData, setLedgerData] = useState(null); + const [ledgerLoading, setLedgerLoading] = useState(false); + const [activeTab, setActiveTab] = useState<'fixed' | 'province' | 'city' | 'settlement' | 'expired' | 'ledger'>('fixed'); // 加载报表数据 const loadReportData = useCallback(async () => { @@ -65,10 +73,32 @@ export default function SystemAccountsTab() { } }, []); + // 加载分类账明细 + const loadLedgerData = useCallback(async () => { + setLedgerLoading(true); + try { + const response = await systemAccountReportService.getAllLedger({ pageSize: 50 }); + if (response.data) { + setLedgerData(response.data); + } + } catch (err) { + console.error('Failed to load ledger data:', err); + } finally { + setLedgerLoading(false); + } + }, []); + useEffect(() => { loadReportData(); }, [loadReportData]); + // 切换到分类账明细时加载数据 + useEffect(() => { + if (activeTab === 'ledger' && !ledgerData && !ledgerLoading) { + loadLedgerData(); + } + }, [activeTab, ledgerData, ledgerLoading, loadLedgerData]); + if (loading) { return (
@@ -127,6 +157,12 @@ export default function SystemAccountsTab() { > 过期收益 +
{/* 内容区域 */} @@ -136,6 +172,7 @@ export default function SystemAccountsTab() { {activeTab === 'city' && } {activeTab === 'settlement' && } {activeTab === 'expired' && } + {activeTab === 'ledger' && } {/* 报表生成时间 */} @@ -392,3 +429,209 @@ function ExpiredRewardsSection({ data }: { data: SystemAccountReportResponse['ex ); } + +// [2026-01-05] 新增:分类账明细组件 +/** + * 分类账明细区域 + */ +function LedgerSection({ + data, + loading, + onRefresh, +}: { + data: AllSystemAccountsLedgerResponse | null; + loading: boolean; + onRefresh: () => void; +}) { + const [expandedAccounts, setExpandedAccounts] = useState>(new Set()); + + const toggleExpand = (accountKey: string) => { + setExpandedAccounts((prev) => { + const newSet = new Set(prev); + if (newSet.has(accountKey)) { + newSet.delete(accountKey); + } else { + newSet.add(accountKey); + } + return newSet; + }); + }; + + if (loading) { + return ( +
+
+ 加载分类账明细中... +
+ ); + } + + if (!data) { + return ( +
+
+ 暂无分类账数据 + +
+
+ ); + } + + const getEntryTypeLabel = (type: string): string => { + return ENTRY_TYPE_LABELS[type] || type; + }; + + const getAccountTypeLabel = (type: string): string => { + return ACCOUNT_TYPE_LABELS[type] || type; + }; + + return ( +
+
+

分类账明细

+ +
+ + {/* 固定系统账户分类账 */} + {data.fixedAccountsLedger && data.fixedAccountsLedger.length > 0 && ( +
+

固定系统账户

+ {data.fixedAccountsLedger.map((account) => ( + toggleExpand(account.accountSequence)} + getEntryTypeLabel={getEntryTypeLabel} + /> + ))} +
+ )} + + {/* 省区域账户分类账 */} + {data.provinceAccountsLedger && data.provinceAccountsLedger.length > 0 && ( +
+

省区域账户 ({data.provinceAccountsLedger.length}个)

+ {data.provinceAccountsLedger.map((account) => ( + toggleExpand(account.accountSequence)} + getEntryTypeLabel={getEntryTypeLabel} + /> + ))} +
+ )} + + {/* 市区域账户分类账 */} + {data.cityAccountsLedger && data.cityAccountsLedger.length > 0 && ( +
+

市区域账户 ({data.cityAccountsLedger.length}个)

+ {data.cityAccountsLedger.map((account) => ( + toggleExpand(account.accountSequence)} + getEntryTypeLabel={getEntryTypeLabel} + /> + ))} +
+ )} + + {(!data.fixedAccountsLedger || data.fixedAccountsLedger.length === 0) && + (!data.provinceAccountsLedger || data.provinceAccountsLedger.length === 0) && + (!data.cityAccountsLedger || data.cityAccountsLedger.length === 0) && ( +
暂无分类账明细数据
+ )} +
+ ); +} + +/** + * 单个账户的分类账卡片 + */ +function LedgerAccountCard({ + title, + subtitle, + ledger, + total, + expanded, + onToggle, + getEntryTypeLabel, +}: { + title: string; + subtitle: string; + ledger: LedgerEntryDTO[]; + total: number; + expanded: boolean; + onToggle: () => void; + getEntryTypeLabel: (type: string) => string; +}) { + return ( +
+
+
+ {title} + {subtitle} +
+
+ {total} 条记录 + {expanded ? '▼' : '▶'} +
+
+ {expanded && ( +
+ {ledger.length > 0 ? ( +
+ + + + + + + + + + + + {ledger.map((entry) => ( + + + + + + + + ))} + +
时间类型金额余额备注
{new Date(entry.createdAt).toLocaleString('zh-CN')} + + {getEntryTypeLabel(entry.entryType)} + + = 0 ? styles.amountPositive : styles.amountNegative}> + {entry.amount >= 0 ? '+' : ''}{formatAmount(entry.amount)} {entry.assetType} + {entry.balanceAfter !== null ? formatAmount(entry.balanceAfter) : '-'}{entry.memo || entry.allocationType || '-'}
+
+ ) : ( +
暂无流水记录
+ )} +
+ )} +
+ ); +} diff --git a/frontend/admin-web/src/infrastructure/api/endpoints.ts b/frontend/admin-web/src/infrastructure/api/endpoints.ts index c528d766..93da4a04 100644 --- a/frontend/admin-web/src/infrastructure/api/endpoints.ts +++ b/frontend/admin-web/src/infrastructure/api/endpoints.ts @@ -214,5 +214,7 @@ export const API_ENDPOINTS = { CITY_SUMMARY: '/v1/system-account-reports/city-summary', OFFLINE_SETTLEMENT: '/v1/system-account-reports/offline-settlement', EXPIRED_REWARDS: '/v1/system-account-reports/expired-rewards', + // [2026-01-05] 新增:所有系统账户分类账明细 + ALL_LEDGER: '/v1/system-account-reports/all-ledger', }, } as const; diff --git a/frontend/admin-web/src/services/systemAccountReportService.ts b/frontend/admin-web/src/services/systemAccountReportService.ts index 1dd99ce0..fe4f4059 100644 --- a/frontend/admin-web/src/services/systemAccountReportService.ts +++ b/frontend/admin-web/src/services/systemAccountReportService.ts @@ -13,6 +13,7 @@ import type { RegionAccountsSummary, OfflineSettlementSummary, ExpiredRewardsSummary, + AllSystemAccountsLedgerResponse, } from '@/types'; /** @@ -68,6 +69,14 @@ export const systemAccountReportService = { async getExpiredRewards(params?: DateRangeParams): Promise> { return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.EXPIRED_REWARDS, { params }); }, + + // [2026-01-05] 新增:获取所有系统账户分类账明细 + /** + * 获取所有系统账户的分类账明细 + */ + async getAllLedger(params?: DateRangeParams & { pageSize?: number }): Promise> { + return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.ALL_LEDGER, { params }); + }, }; export default systemAccountReportService; diff --git a/frontend/admin-web/src/types/system-account.types.ts b/frontend/admin-web/src/types/system-account.types.ts index 26c26048..82248014 100644 --- a/frontend/admin-web/src/types/system-account.types.ts +++ b/frontend/admin-web/src/types/system-account.types.ts @@ -120,3 +120,68 @@ export const RIGHT_TYPE_LABELS: Record = { CITY_REGION: '市区域权益', COMMUNITY: '社区权益', }; + +// [2026-01-05] 新增:分类账明细类型 +/** + * 分类账条目 + */ +export interface LedgerEntryDTO { + id: string; + entryType: string; + amount: number; + assetType: string; + balanceAfter: number | null; + refOrderId: string | null; + refTxHash: string | null; + memo: string | null; + allocationType: string | null; + createdAt: string; +} + +/** + * 固定账户分类账 + */ +export interface FixedAccountLedger { + accountSequence: string; + accountType: string; + ledger: LedgerEntryDTO[]; + total: number; +} + +/** + * 区域账户分类账 + */ +export interface RegionAccountLedger { + accountSequence: string; + regionCode: string; + regionName: string; + ledger: LedgerEntryDTO[]; + total: number; +} + +/** + * 所有系统账户分类账响应 + */ +export interface AllSystemAccountsLedgerResponse { + fixedAccountsLedger: FixedAccountLedger[]; + provinceAccountsLedger: RegionAccountLedger[]; + cityAccountsLedger: RegionAccountLedger[]; +} + +/** + * 流水类型显示名称映射 + */ +export const ENTRY_TYPE_LABELS: Record = { + DEPOSIT: '充值', + WITHDRAW: '提现', + TRANSFER_IN: '转入', + TRANSFER_OUT: '转出', + REWARD_INCOME: '奖励收入', + REWARD_SETTLE: '奖励结算', + REWARD_EXPIRED: '奖励过期', + PLANTING_PAYMENT: '认种支付', + OFFLINE_SETTLEMENT: '面对面结算', + FEE_DEDUCTION: '手续费扣除', + ALLOCATION_IN: '分配收入', + ALLOCATION_OUT: '分配支出', +};