feat(admin-web): 省/市区域账户添加点击查看明细功能

RegionAccountsSection 新增:
- 每行添加"查看明细"按钮,点击展开该账户分类账流水
- 明细表包含 时间/类型/金额/余额/来源账户/来源备注/备注 7列
- 复用 getAllLedger API 的 provinceAccountsLedger/cityAccountsLedger 数据
- 行点击和按钮点击均可展开/收起
- 新增 clickableRow/selectedRow CSS 样式

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-28 20:37:12 -08:00
parent bf50810830
commit 6a659ca718
2 changed files with 138 additions and 1 deletions

View File

@ -683,6 +683,22 @@
}
}
/* [2026-02-28] 新增:区域账户表格行可点击样式 */
.clickableRow {
cursor: pointer;
transition: background-color 0.15s ease;
&:hover {
background-color: #eff6ff !important;
}
}
.selectedRow {
cursor: pointer;
background-color: #eff6ff !important;
border-left: 3px solid #3b82f6;
}
.accountLedgerSection {
margin-top: 12px;
padding-top: 12px;

View File

@ -482,10 +482,57 @@ function FixedAccountsSection({ data }: { data: SystemAccountReportResponse['fix
*
* [2026-01-05] USDT改为绿积分
* [2026-01-07]
* [2026-02-28]
*/
function RegionAccountsSection({ data, type }: { data: RegionAccountsSummary; type: 'province' | 'city' }) {
const typeLabel = type === 'province' ? '省' : '市';
// [2026-02-28] 新增:账户明细展开相关状态
const [selectedAccount, setSelectedAccount] = useState<string | null>(null);
const [allLedgerData, setAllLedgerData] = useState<AllSystemAccountsLedgerResponse | null>(null);
const [ledgerLoading, setLedgerLoading] = useState(false);
// 加载所有系统账户分类账数据
const loadAllLedger = useCallback(async () => {
if (allLedgerData) return;
setLedgerLoading(true);
try {
const response = await systemAccountReportService.getAllLedger({ pageSize: 100 });
if (response.data) {
setAllLedgerData(response.data);
}
} catch (err) {
console.error('Failed to load ledger data:', err);
} finally {
setLedgerLoading(false);
}
}, [allLedgerData]);
// 获取指定区域账户的分类账数据
const getAccountLedger = useCallback((accountSequence: string): LedgerEntryDTO[] => {
const ledgerKey = type === 'province' ? 'provinceAccountsLedger' : 'cityAccountsLedger';
const regionLedgers = allLedgerData?.[ledgerKey];
if (!regionLedgers) return [];
const accountLedger = regionLedgers.find(
(item) => item.accountSequence === accountSequence
);
return accountLedger?.ledger || [];
}, [allLedgerData, type]);
// 点击账户行展开/收起明细
const handleSelectAccount = async (accountSequence: string) => {
if (selectedAccount === accountSequence) {
setSelectedAccount(null);
return;
}
if (!allLedgerData) {
await loadAllLedger();
}
setSelectedAccount(accountSequence);
};
const currentLedger = selectedAccount ? getAccountLedger(selectedAccount) : [];
return (
<div className={styles.section}>
<h3 className={styles.sectionTitle}>{typeLabel}</h3>
@ -522,11 +569,16 @@ function RegionAccountsSection({ data, type }: { data: RegionAccountsSummary; ty
<th> (绿)</th>
<th> (绿)</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{data.accounts.map((account) => (
<tr key={account.id}>
<tr
key={account.id}
className={selectedAccount === account.accountSequence ? styles.selectedRow : styles.clickableRow}
onClick={() => handleSelectAccount(account.accountSequence)}
>
{/* [2026-01-07] 更新:使用 getAccountDisplayName 解析区域代码为省市名称 */}
<td>{account.regionCode ? getAccountDisplayName(account.regionCode) : '-'}</td>
<td>{formatAmount(account.usdtBalance)}</td>
@ -537,6 +589,15 @@ function RegionAccountsSection({ data, type }: { data: RegionAccountsSummary; ty
{account.status === 'ACTIVE' ? '正常' : account.status}
</span>
</td>
<td>
<button
className={`${styles.viewLedgerButton} ${selectedAccount === account.accountSequence ? styles.viewLedgerButtonActive : ''}`}
onClick={(e) => { e.stopPropagation(); handleSelectAccount(account.accountSequence); }}
disabled={ledgerLoading}
>
{ledgerLoading && !allLedgerData ? '加载中...' : selectedAccount === account.accountSequence ? '收起明细' : '查看明细'}
</button>
</td>
</tr>
))}
</tbody>
@ -545,6 +606,66 @@ function RegionAccountsSection({ data, type }: { data: RegionAccountsSummary; ty
) : (
<div className={styles.emptyTable}>{typeLabel}</div>
)}
{/* [2026-02-28] 新增:选中账户的分类账明细展开区域 */}
{selectedAccount && (
<div className={styles.sharedLedgerSection}>
<div className={styles.sharedLedgerHeader}>
<h4 className={styles.sharedLedgerTitle}>
{getAccountDisplayName(selectedAccount)} -
</h4>
<button
className={styles.closeLedgerButton}
onClick={() => setSelectedAccount(null)}
>
</button>
</div>
{ledgerLoading ? (
<div className={styles.loading}>
<div className={styles.spinner} />
<span>...</span>
</div>
) : currentLedger.length > 0 ? (
<div className={styles.tableWrapper}>
<table className={styles.table}>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{currentLedger.map((entry) => (
<tr key={entry.id}>
<td>{new Date(entry.createdAt).toLocaleString('zh-CN')}</td>
<td>
<span className={styles.entryTypeBadge}>
{ENTRY_TYPE_LABELS[entry.entryType] || entry.entryType}
</span>
</td>
<td className={entry.amount >= 0 ? styles.amountPositive : styles.amountNegative}>
{entry.amount >= 0 ? '+' : ''}{formatAmount(entry.amount)} {getAssetTypeLabel(entry.assetType)}
</td>
<td>{entry.balanceAfter !== null ? formatAmount(entry.balanceAfter) : '-'}</td>
<td className={styles.sourceAccount}>{entry.sourceAccountSequence || '-'}</td>
<td className={styles.memo}>{entry.sourceMemo || '-'}</td>
<td className={styles.memo}>{entry.memo || entry.allocationType || '-'}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className={styles.emptyLedger}></div>
)}
</div>
)}
</div>
);
}