hyapp-admin-platform/finance/src/components/FinanceRechargeDetailList.jsx
2026-07-02 10:54:00 +08:00

208 lines
8.6 KiB
JavaScript

import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import TextField from "@mui/material/TextField";
import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx";
import {
formatAmount,
formatMicroMoney,
formatTime,
formatUsdMinor,
formatPercent,
rechargeTypeLabel,
} from "../format.js";
export function FinanceRechargeDetailList({ bills, error, filters, loading, onFiltersChange, onReload, options }) {
const items = bills.items || [];
const page = Number(bills.page || filters.page || 1);
const pageSize = Number(bills.pageSize || 50);
const total = Number(bills.total || 0);
const hasNextPage = page * pageSize < total;
return (
<section className="finance-panel finance-list">
<div className="finance-toolbar finance-toolbar--recharge-details">
<TextField
label="APP"
select
value={filters.appCode}
onChange={(event) => onFiltersChange({ appCode: event.target.value })}
>
<MenuItem value="">全部 APP</MenuItem>
{options.apps.map((app) => (
<MenuItem key={app.appCode} value={app.appCode}>
{app.appName || app.appCode}
</MenuItem>
))}
</TextField>
<TimeRangeFilter
label="充值时间 · 中国时区"
value={filters.timeRange}
onChange={(timeRange) => onFiltersChange({ timeRange })}
/>
<TextField
label="订单搜索"
value={filters.keyword}
onChange={(event) => onFiltersChange({ keyword: event.target.value })}
/>
<Button startIcon={<RefreshOutlined fontSize="small" />} variant="outlined" onClick={onReload}>
刷新列表
</Button>
</div>
{error ? <div className="finance-error">{error}</div> : null}
{loading && !items.length ? <div className="finance-skeleton" /> : null}
{!loading || items.length ? (
<TableContainer className="finance-table-wrap">
<Table className="finance-table finance-table--recharge-details" size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell>APP</TableCell>
<TableCell align="right">充值总额</TableCell>
<TableCell>充值来源</TableCell>
<TableCell>充值订单号</TableCell>
<TableCell>美金换算</TableCell>
<TableCell>账单金额</TableCell>
<TableCell>用户实付</TableCell>
<TableCell>三方税率扣款</TableCell>
<TableCell className="finance-time-cell">充值时间</TableCell>
</TableRow>
</TableHead>
<TableBody>
{items.length ? (
items.map((item) => (
<TableRow key={item.id || item.transactionId}>
<TableCell>{appName(item, options.apps)}</TableCell>
<TableCell align="right">{coinText(item.coinAmount)}</TableCell>
<TableCell>{rechargeTypeLabel(item.rechargeType)}</TableCell>
<TableCell>
<Stacked
primary={item.transactionId || "-"}
secondary={secondaryOrderNo(item)}
/>
</TableCell>
<TableCell>{exchangeText(item)}</TableCell>
<TableCell>{billAmountText(item)}</TableCell>
<TableCell>{userPaidText(item)}</TableCell>
<TableCell>{taxDeductionText(item)}</TableCell>
<TableCell className="finance-time-cell">
{formatTime(item.createdAtMs)}
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell align="center" colSpan={9}>
当前无数据
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
) : null}
{total > pageSize ? (
<div className="finance-pagination">
<Button
disabled={page <= 1 || loading}
variant="outlined"
onClick={() => onFiltersChange({ page: page - 1 })}
>
上一页
</Button>
<span>
{page} / {total}
</span>
<Button
disabled={!hasNextPage || loading}
variant="outlined"
onClick={() => onFiltersChange({ page: page + 1 })}
>
下一页
</Button>
</div>
) : null}
</section>
);
}
function Stacked({ primary, secondary }) {
return (
<span className="finance-stack">
<span>{primary || "-"}</span>
<small>{secondary || ""}</small>
</span>
);
}
function appName(item, apps) {
return item.appName || apps.find((app) => app.appCode === item.appCode)?.appName || item.appCode || "-";
}
function secondaryOrderNo(item) {
return [item.commandId, item.externalRef].filter(Boolean).join(" / ");
}
function coinText(value) {
const text = formatAmount(value);
return text === "-" ? "-" : `${text} 金币`;
}
function exchangeText(item) {
if (hasNumber(item.exchangeCoinAmount) || hasNumber(item.exchangeUsdMinorAmount)) {
return `${coinText(item.exchangeCoinAmount)} = ${formatUsdMinor(item.exchangeUsdMinorAmount, "USD")}`;
}
return formatUsdMinor(item.usdMinorAmount, "USD");
}
function billAmountText(item) {
if (item.billAmountText) {
return item.billAmountText;
}
const amount = hasNumber(item.billAmountMinor)
? item.billAmountMinor
: hasNumber(item.providerAmountMinor)
? item.providerAmountMinor
: item.usdMinorAmount;
return formatUsdMinor(amount, item.currencyCode || "USD");
}
function userPaidText(item) {
if (item.userPaidText) {
return item.userPaidText;
}
if (hasNumber(item.userPaidAmountMinor)) {
return formatUsdMinor(item.userPaidAmountMinor, item.currencyCode || "USD");
}
if (hasNumber(item.providerAmountMinor)) {
return formatUsdMinor(item.providerAmountMinor, item.currencyCode || "USD");
}
if (hasNumber(item.userPaidAmountMicro)) {
return formatMicroMoney(item.userPaidAmountMicro, item.currencyCode || "USD");
}
return "-";
}
function taxDeductionText(item) {
const rate = formatPercent(item.taxRate);
const amount =
item.taxDeductionText ||
(hasNumber(item.taxDeductionMinor) ? formatUsdMinor(item.taxDeductionMinor, item.currencyCode || "USD") : "");
if (amount && rate) {
return `${amount} / ${rate}`;
}
return amount || rate || "-";
}
function hasNumber(value) {
if (value === null || value === undefined || value === "") {
return false;
}
return Number.isFinite(Number(value));
}