ui修改
This commit is contained in:
parent
88eae83dbe
commit
b8b5182c67
@ -394,14 +394,6 @@ export function FinanceApp() {
|
||||
canViewWithdrawals={session.canViewWithdrawalApplications}
|
||||
loading={optionsState.loading || rechargeDetailsState.loading || myApplicationsState.loading || applicationsState.loading || withdrawalsState.loading}
|
||||
session={session}
|
||||
onRefresh={() => {
|
||||
loadOptions();
|
||||
loadRechargeDetails();
|
||||
loadRechargeSummary();
|
||||
loadMyApplications();
|
||||
loadApplications();
|
||||
loadWithdrawals();
|
||||
}}
|
||||
onViewChange={setActiveView}
|
||||
>
|
||||
{activeView === "rechargeDetails" && session.canViewAppRechargeDetails ? (
|
||||
|
||||
@ -41,7 +41,10 @@ test("keeps create application dialog open when backdrop is clicked", async () =
|
||||
</ToastProvider>
|
||||
);
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "添加" }));
|
||||
await screen.findByRole("button", { name: "添加" });
|
||||
expect(document.querySelector(".finance-header")).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "添加" }));
|
||||
expect(within(screen.getByRole("dialog")).getByRole("heading", { name: "发起申请" })).toBeInTheDocument();
|
||||
|
||||
const backdrop = document.querySelector(".MuiBackdrop-root");
|
||||
|
||||
@ -9,6 +9,8 @@ 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 { useState } from "react";
|
||||
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
|
||||
import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx";
|
||||
import {
|
||||
EMPTY_RECHARGE_SUMMARY,
|
||||
@ -33,12 +35,14 @@ export function FinanceRechargeDetailList({
|
||||
summary,
|
||||
summaryLoading,
|
||||
}) {
|
||||
const [showCoinColumns, setShowCoinColumns] = useState(false);
|
||||
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;
|
||||
const regionOptions = regions || [];
|
||||
const visibleColumnCount = showCoinColumns ? 9 : 7;
|
||||
const pendingGoogleTxIds = items
|
||||
.filter((item) => item.rechargeType === "google_play_recharge" && !item.paidSyncedAtMs)
|
||||
.map((item) => item.transactionId)
|
||||
@ -46,7 +50,7 @@ export function FinanceRechargeDetailList({
|
||||
|
||||
return (
|
||||
<section className="finance-panel finance-list">
|
||||
<RechargeSummaryCards loading={summaryLoading} summary={summary} />
|
||||
<RechargeSummaryCards loading={summaryLoading} showCoins={showCoinColumns} summary={summary} />
|
||||
<div className="finance-toolbar finance-toolbar--recharge-details">
|
||||
<TextField
|
||||
label="APP"
|
||||
@ -86,6 +90,14 @@ export function FinanceRechargeDetailList({
|
||||
value={filters.keyword}
|
||||
onChange={(event) => onFiltersChange({ keyword: event.target.value })}
|
||||
/>
|
||||
<label className="finance-switch-field">
|
||||
<AdminSwitch
|
||||
checked={showCoinColumns}
|
||||
label="金币显示"
|
||||
onChange={(event) => setShowCoinColumns(event.target.checked)}
|
||||
/>
|
||||
<span>金币显示</span>
|
||||
</label>
|
||||
<Button
|
||||
disabled={googlePaidRefreshing || !filters.appCode || !pendingGoogleTxIds.length}
|
||||
startIcon={<CloudSyncOutlined fontSize="small" />}
|
||||
@ -103,14 +115,24 @@ export function FinanceRechargeDetailList({
|
||||
{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>
|
||||
<Table
|
||||
className={[
|
||||
"finance-table",
|
||||
"finance-table--recharge-details",
|
||||
showCoinColumns ? "finance-table--recharge-details-with-coins" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
size="small"
|
||||
stickyHeader
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>APP</TableCell>
|
||||
<TableCell align="right">充值总额</TableCell>
|
||||
{showCoinColumns ? <TableCell align="right">充值金币</TableCell> : null}
|
||||
<TableCell>充值来源</TableCell>
|
||||
<TableCell>充值订单号</TableCell>
|
||||
<TableCell>美金换算</TableCell>
|
||||
{showCoinColumns ? <TableCell>金币换算</TableCell> : null}
|
||||
<TableCell>账单金额</TableCell>
|
||||
<TableCell>用户实付</TableCell>
|
||||
<TableCell>三方税率扣款</TableCell>
|
||||
@ -122,7 +144,9 @@ export function FinanceRechargeDetailList({
|
||||
items.map((item) => (
|
||||
<TableRow key={item.id || item.transactionId}>
|
||||
<TableCell>{appName(item, options.apps)}</TableCell>
|
||||
<TableCell align="right">{coinText(item.coinAmount)}</TableCell>
|
||||
{showCoinColumns ? (
|
||||
<TableCell align="right">{coinText(item.coinAmount)}</TableCell>
|
||||
) : null}
|
||||
<TableCell>{rechargeTypeLabel(item.rechargeType)}</TableCell>
|
||||
<TableCell>
|
||||
<Stacked
|
||||
@ -130,7 +154,7 @@ export function FinanceRechargeDetailList({
|
||||
secondary={secondaryOrderNo(item)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{exchangeText(item)}</TableCell>
|
||||
{showCoinColumns ? <TableCell>{exchangeText(item)}</TableCell> : null}
|
||||
<TableCell>{billAmountText(item)}</TableCell>
|
||||
<TableCell>{userPaidText(item)}</TableCell>
|
||||
<TableCell>{taxDeductionText(item)}</TableCell>
|
||||
@ -141,7 +165,7 @@ export function FinanceRechargeDetailList({
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell align="center" colSpan={9}>
|
||||
<TableCell align="center" colSpan={visibleColumnCount}>
|
||||
当前无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@ -175,7 +199,7 @@ export function FinanceRechargeDetailList({
|
||||
);
|
||||
}
|
||||
|
||||
function RechargeSummaryCards({ loading, summary }) {
|
||||
function RechargeSummaryCards({ loading, showCoins, summary }) {
|
||||
const data = summary || EMPTY_RECHARGE_SUMMARY;
|
||||
const cards = [
|
||||
{ bucket: data.total, key: "total", label: "充值总和" },
|
||||
@ -193,7 +217,7 @@ function RechargeSummaryCards({ loading, summary }) {
|
||||
<small>
|
||||
{loading
|
||||
? "统计中"
|
||||
: `${formatAmount(bucket.billCount)} 笔 · ${formatAmount(bucket.coinAmount)} 金币`}
|
||||
: summaryMetaText(bucket, showCoins)}
|
||||
</small>
|
||||
</div>
|
||||
))}
|
||||
@ -201,6 +225,14 @@ function RechargeSummaryCards({ loading, summary }) {
|
||||
);
|
||||
}
|
||||
|
||||
function summaryMetaText(bucket, showCoins) {
|
||||
const billCount = `${formatAmount(bucket.billCount)} 笔`;
|
||||
if (!showCoins) {
|
||||
return billCount;
|
||||
}
|
||||
return `${billCount} · ${formatAmount(bucket.coinAmount)} 金币`;
|
||||
}
|
||||
|
||||
function Stacked({ primary, secondary }) {
|
||||
return (
|
||||
<span className="finance-stack">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { expect, test, vi } from "vitest";
|
||||
import { FinanceRechargeDetailList } from "./FinanceRechargeDetailList.jsx";
|
||||
|
||||
@ -14,6 +14,7 @@ const baseProps = {
|
||||
appCode: "",
|
||||
keyword: "",
|
||||
page: 1,
|
||||
regionId: "",
|
||||
timeRange: { endMs: "", startMs: "" },
|
||||
},
|
||||
loading: false,
|
||||
@ -25,49 +26,63 @@ const baseProps = {
|
||||
{ appCode: "aslan", appName: "Aslan" },
|
||||
],
|
||||
},
|
||||
summary: {
|
||||
googlePlay: { billCount: 1, coinAmount: 90000, usdMinorAmount: 999 },
|
||||
thirdParty: { billCount: 0, coinAmount: 0, usdMinorAmount: 0 },
|
||||
total: { billCount: 1, coinAmount: 90000, usdMinorAmount: 999 },
|
||||
},
|
||||
summaryLoading: false,
|
||||
};
|
||||
|
||||
test("renders app recharge detail columns and values", () => {
|
||||
test("hides coin recharge columns by default", () => {
|
||||
render(
|
||||
<FinanceRechargeDetailList
|
||||
{...baseProps}
|
||||
bills={{
|
||||
...baseProps.bills,
|
||||
items: [
|
||||
{
|
||||
appCode: "aslan",
|
||||
coinAmount: 90000,
|
||||
commandId: "cmd-1",
|
||||
createdAtMs: 1760000000000,
|
||||
currencyCode: "SAR",
|
||||
exchangeCoinAmount: 90000,
|
||||
exchangeUsdMinorAmount: 999,
|
||||
externalRef: "GPA.1",
|
||||
id: "tx-google",
|
||||
providerAmountMinor: 1299,
|
||||
rechargeType: "google_play_recharge",
|
||||
taxDeductionMinor: 195,
|
||||
taxRate: 0.15,
|
||||
transactionId: "tx-google",
|
||||
usdMinorAmount: 999,
|
||||
},
|
||||
],
|
||||
items: [rechargeBillFixture()],
|
||||
total: 1,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("switch", { name: "金币显示" })).not.toBeChecked();
|
||||
expect(screen.queryByRole("columnheader", { name: "充值金币" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("columnheader", { name: "金币换算" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("90,000 金币")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("90,000 金币 = USD 9.99")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("1 笔 · 90,000 金币")).not.toBeInTheDocument();
|
||||
expect(screen.getAllByText("1 笔")).toHaveLength(2);
|
||||
expect(screen.getByRole("columnheader", { name: "账单金额" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "用户实付" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders app recharge coin columns and values when switch is enabled", () => {
|
||||
render(
|
||||
<FinanceRechargeDetailList
|
||||
{...baseProps}
|
||||
bills={{
|
||||
...baseProps.bills,
|
||||
items: [rechargeBillFixture()],
|
||||
total: 1,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("switch", { name: "金币显示" }));
|
||||
|
||||
expect(screen.getByRole("columnheader", { name: "APP" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "充值总额" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "充值金币" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "充值来源" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "充值订单号" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "美金换算" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "金币换算" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "账单金额" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "用户实付" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "三方税率扣款" })).toBeInTheDocument();
|
||||
expect(screen.getByText("Aslan")).toBeInTheDocument();
|
||||
expect(screen.getByText("90,000 金币")).toBeInTheDocument();
|
||||
expect(screen.getByText("谷歌充值")).toBeInTheDocument();
|
||||
expect(screen.getAllByText("1 笔 · 90,000 金币")).toHaveLength(2);
|
||||
expect(screen.getAllByText("谷歌充值").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText("tx-google")).toBeInTheDocument();
|
||||
expect(screen.getByText("cmd-1 / GPA.1")).toBeInTheDocument();
|
||||
expect(screen.getByText("90,000 金币 = USD 9.99")).toBeInTheDocument();
|
||||
@ -79,6 +94,32 @@ test("renders app recharge detail columns and values", () => {
|
||||
test("keeps empty recharge detail table colspan aligned with visible columns", () => {
|
||||
const { container } = render(<FinanceRechargeDetailList {...baseProps} />);
|
||||
|
||||
expect(container.querySelector("tbody td")?.colSpan).toBe(9);
|
||||
expect(container.querySelector("tbody td")?.colSpan).toBe(7);
|
||||
expect(screen.getByText("当前无数据")).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("switch", { name: "金币显示" }));
|
||||
expect(container.querySelector("tbody td")?.colSpan).toBe(9);
|
||||
});
|
||||
|
||||
function rechargeBillFixture() {
|
||||
return {
|
||||
appCode: "aslan",
|
||||
coinAmount: 90000,
|
||||
commandId: "cmd-1",
|
||||
createdAtMs: 1760000000000,
|
||||
currencyCode: "SAR",
|
||||
exchangeCoinAmount: 90000,
|
||||
exchangeUsdMinorAmount: 999,
|
||||
externalRef: "GPA.1",
|
||||
id: "tx-google",
|
||||
paidSyncedAtMs: 1760000100000,
|
||||
providerAmountMinor: 1299,
|
||||
providerTaxMicro: 1950000,
|
||||
rechargeType: "google_play_recharge",
|
||||
transactionId: "tx-google",
|
||||
usdMinorAmount: 999,
|
||||
userPaidAmountMicro: 13000000,
|
||||
userPaidCurrencyCode: "SAR",
|
||||
userPaidText: "SAR 12.99",
|
||||
};
|
||||
}
|
||||
|
||||
@ -2,8 +2,6 @@ import AssignmentOutlined from "@mui/icons-material/AssignmentOutlined";
|
||||
import FactCheckOutlined from "@mui/icons-material/FactCheckOutlined";
|
||||
import PaymentsOutlined from "@mui/icons-material/PaymentsOutlined";
|
||||
import ReceiptLongOutlined from "@mui/icons-material/ReceiptLongOutlined";
|
||||
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
|
||||
import Button from "@mui/material/Button";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
|
||||
const views = [
|
||||
@ -13,7 +11,7 @@ const views = [
|
||||
{ icon: PaymentsOutlined, id: "withdrawals", label: "用户提现申请" }
|
||||
];
|
||||
|
||||
export function FinanceShell({ activeView, canAudit, canCreate, canViewRechargeDetails, canViewWithdrawals, children, loading, onRefresh, onViewChange, session }) {
|
||||
export function FinanceShell({ activeView, canAudit, canCreate, canViewRechargeDetails, canViewWithdrawals, children, loading, onViewChange, session }) {
|
||||
const visibleViews = views.filter((view) => {
|
||||
if (view.id === "rechargeDetails") {
|
||||
return canViewRechargeDetails;
|
||||
@ -26,8 +24,6 @@ export function FinanceShell({ activeView, canAudit, canCreate, canViewRechargeD
|
||||
}
|
||||
return canCreate;
|
||||
});
|
||||
const activeLabel = visibleViews.find((view) => view.id === activeView)?.label || visibleViews[0]?.label || "财务系统";
|
||||
|
||||
return (
|
||||
<main className="finance-shell">
|
||||
<aside className="finance-sidebar">
|
||||
@ -60,15 +56,6 @@ export function FinanceShell({ activeView, canAudit, canCreate, canViewRechargeD
|
||||
</div>
|
||||
</aside>
|
||||
<section className="finance-main">
|
||||
<header className="finance-header">
|
||||
<div>
|
||||
<span>HYApp</span>
|
||||
<h1>{activeLabel}</h1>
|
||||
</div>
|
||||
<Button startIcon={<RefreshOutlined fontSize="small" />} variant="outlined" onClick={onRefresh}>
|
||||
刷新
|
||||
</Button>
|
||||
</header>
|
||||
{loading ? <LinearProgress className="finance-progress" /> : null}
|
||||
<div className="finance-content">{children}</div>
|
||||
</section>
|
||||
|
||||
@ -54,8 +54,7 @@ a {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.finance-brand strong,
|
||||
.finance-header h1 {
|
||||
.finance-brand strong {
|
||||
color: var(--text-primary);
|
||||
font-size: 18px;
|
||||
line-height: 1.3;
|
||||
@ -104,36 +103,25 @@ a {
|
||||
}
|
||||
|
||||
.finance-user small,
|
||||
.finance-header span,
|
||||
.finance-stack small {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.finance-main {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
grid-template-rows: auto auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.finance-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 72px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-card);
|
||||
padding: 0 var(--space-6);
|
||||
}
|
||||
|
||||
.finance-header h1 {
|
||||
margin: 2px 0 0;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.finance-progress {
|
||||
flex: 0 0 auto;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.finance-content {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
@ -173,7 +161,20 @@ a {
|
||||
}
|
||||
|
||||
.finance-toolbar--recharge-details {
|
||||
grid-template-columns: minmax(150px, 1fr) minmax(130px, 0.85fr) minmax(250px, 1.3fr) minmax(160px, 1fr) auto auto;
|
||||
grid-template-columns: minmax(150px, 1fr) minmax(130px, 0.85fr) minmax(250px, 1.3fr) minmax(160px, 1fr) auto auto auto;
|
||||
}
|
||||
|
||||
.finance-switch-field {
|
||||
display: inline-flex;
|
||||
height: var(--control-height);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-1);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: var(--control-font-size);
|
||||
font-weight: 650;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.finance-summary-grid {
|
||||
@ -224,6 +225,10 @@ a {
|
||||
}
|
||||
|
||||
.finance-table--recharge-details {
|
||||
min-width: 1240px;
|
||||
}
|
||||
|
||||
.finance-table--recharge-details-with-coins {
|
||||
min-width: 1480px;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user