208 lines
8.6 KiB
JavaScript
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));
|
|
}
|