hyapp-admin-platform/src/features/payment/pages/RechargeProductConfigPage.jsx
2026-06-11 01:01:53 +08:00

391 lines
14 KiB
JavaScript

import AddOutlined from "@mui/icons-material/AddOutlined";
import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined";
import EditOutlined from "@mui/icons-material/EditOutlined";
import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField";
import { useMemo } from "react";
import { useRechargeProductsPage } from "@/features/payment/hooks/useRechargeProductsPage.js";
import { Button } from "@/shared/ui/Button.jsx";
import {
AdminFormAmountField,
AdminFormDialog,
AdminFormFieldGrid,
AdminFormSection,
AdminFormSwitchField,
} from "@/shared/ui/AdminFormDialog.jsx";
import {
AdminActionIconButton,
AdminListBody,
AdminListPage,
AdminListToolbar,
AdminRowActions,
} from "@/shared/ui/AdminListLayout.jsx";
import { DataState } from "@/shared/ui/DataState.jsx";
import { DataTable } from "@/shared/ui/DataTable.jsx";
import { MultiValueAutocomplete } from "@/shared/ui/MultiValueAutocomplete.jsx";
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
import { createRegionColumnFilter } from "@/shared/ui/RegionFilterControl.jsx";
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
import { formatMillis } from "@/shared/utils/time.js";
const statusOptions = [
["", "全部状态"],
["active", "上架"],
["disabled", "下架"],
];
const platformOptions = [
["", "全部平台"],
["web", "Web"],
["android", "Android"],
["ios", "iOS"],
];
const audienceOptions = [
["", "全部用户类型"],
["normal", "普通用户"],
["coin_seller", "币商"],
];
export function RechargeProductConfigPage() {
const page = useRechargeProductsPage();
const items = page.data?.items || [];
const regionLabels = useMemo(() => regionLabelMap(page.regionOptions), [page.regionOptions]);
const regionValues = useMemo(() => page.regionOptions.map((option) => option.value), [page.regionOptions]);
const columns = useMemo(() => productColumns(page, regionLabels), [page, regionLabels]);
const saving = page.loadingAction === "create" || page.loadingAction === "edit";
const canSubmit = page.editingItem ? page.abilities.canUpdateProduct : page.abilities.canCreateProduct;
return (
<AdminListPage>
<AdminListToolbar
actions={
page.abilities.canCreateProduct ? (
<Button
startIcon={<AddOutlined fontSize="small" />}
variant="primary"
onClick={page.openCreate}
>
新增内购
</Button>
) : null
}
/>
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
<AdminListBody>
<DataTable
columns={columns}
items={items}
minWidth="1180px"
pagination={{
page: page.page,
pageSize: page.pageSize,
total: page.data?.total || 0,
onPageChange: page.setPage,
}}
rowKey={(item) => item.productId}
/>
</AdminListBody>
</DataState>
<AdminFormDialog
loading={saving}
open={Boolean(page.activeAction)}
size="compact"
submitDisabled={!canSubmit || saving}
title={page.editingItem ? "编辑内购配置" : "新增内购配置"}
onClose={page.closeDialog}
onSubmit={page.submitProduct}
>
<AdminFormSection title="商品信息">
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
<TextField
disabled={!canSubmit}
label="产品名称"
required
value={page.form.productName}
onChange={(event) => page.setForm({ productName: event.target.value })}
/>
<TextField
disabled={!canSubmit}
label="平台"
required
select
value={page.form.platform}
onChange={(event) => page.setForm({ platform: event.target.value })}
>
<MenuItem value="web">Web</MenuItem>
<MenuItem value="android">Android</MenuItem>
<MenuItem value="ios">iOS</MenuItem>
</TextField>
<TextField
disabled={!canSubmit}
label="用户类型"
required
select
value={page.form.audienceType}
onChange={(event) => page.setForm({ audienceType: event.target.value })}
>
<MenuItem value="normal">普通用户</MenuItem>
<MenuItem value="coin_seller">币商</MenuItem>
</TextField>
</AdminFormFieldGrid>
</AdminFormSection>
<AdminFormSection title="价格配置">
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
<AdminFormAmountField
disabled={!canSubmit}
inputProps={{ inputMode: "decimal" }}
label="金额USDT"
required
unit="USDT"
value={page.form.amountUsdt}
onChange={(event) => page.setForm({ amountUsdt: event.target.value })}
/>
<TextField
disabled={!canSubmit}
inputProps={{ min: 1, step: 1 }}
label="获得金币数"
required
type="number"
value={page.form.coinAmount}
onChange={(event) => page.setForm({ coinAmount: event.target.value })}
/>
</AdminFormFieldGrid>
</AdminFormSection>
<AdminFormSection title="投放设置">
<MultiValueAutocomplete
disabled={!canSubmit}
label="支持区域"
labels={regionLabels}
loading={page.loadingRegions}
options={regionValues}
required
value={page.form.regionIds}
onChange={(regionIds) => page.setForm({ regionIds })}
/>
<AdminFormSwitchField
checked={Boolean(page.form.enabled)}
checkedLabel="上架"
disabled={!canSubmit}
label="是否上架"
uncheckedLabel="下架"
onChange={(checked) => page.setForm({ enabled: checked })}
/>
</AdminFormSection>
<AdminFormSection title="描述">
<TextField
disabled={!canSubmit}
label="描述"
multiline
minRows={2}
value={page.form.description}
onChange={(event) => page.setForm({ description: event.target.value })}
/>
</AdminFormSection>
</AdminFormDialog>
</AdminListPage>
);
}
function productColumns(page, regionLabels) {
const columns = [
{
key: "product",
label: "产品",
render: (item) => <Stack primary={item.productName} secondary={item.productCode || "-"} />,
filter: createTextColumnFilter({
placeholder: "搜索产品、描述",
value: page.keyword,
onChange: page.setKeyword,
}),
width: "minmax(240px, 1.2fr)",
},
{
key: "amount",
label: "金额 / 金币",
render: (item) => (
<Stack
primary={`${item.amountUsdt || formatMicroAmount(item.amountUsdtMicro)} USDT`}
secondary={`${formatNumber(item.coinAmount)} 金币`}
/>
),
width: "minmax(160px, 0.75fr)",
},
{
key: "platform",
label: "平台",
render: (item) => platformLabel(item.platform),
filter: createOptionsColumnFilter({
options: platformOptions,
placeholder: "搜索平台",
value: page.platform,
onChange: page.setPlatform,
}),
width: "minmax(120px, 0.55fr)",
},
{
key: "audienceType",
label: "用户类型",
render: (item) => audienceLabel(item.audienceType),
filter: createOptionsColumnFilter({
options: audienceOptions,
placeholder: "搜索用户类型",
value: page.audienceType,
onChange: page.setAudienceType,
}),
width: "minmax(130px, 0.6fr)",
},
{
key: "regionIds",
label: "支持区域",
render: (item) => regionListLabel(regionLabels, item.regionIds),
filter: createRegionColumnFilter({
loading: page.loadingRegions,
options: page.regionOptions,
value: page.regionId,
onChange: page.setRegionId,
}),
width: "minmax(220px, 1fr)",
},
{
key: "status",
label: "状态",
render: (item) => <ProductStatusSwitch item={item} page={page} />,
filter: createOptionsColumnFilter({
options: statusOptions,
placeholder: "搜索状态",
value: page.status,
onChange: page.setStatus,
}),
width: "minmax(110px, 0.5fr)",
},
{
key: "description",
label: "描述",
render: (item) => item.description || "-",
width: "minmax(180px, 0.9fr)",
},
{
key: "updatedAtMs",
label: "更新时间",
render: (item) => formatMillis(item.updatedAtMs),
width: "minmax(160px, 0.75fr)",
},
];
if (!page.abilities.canUpdateProduct && !page.abilities.canDeleteProduct) {
return columns;
}
return [
...columns,
{
key: "actions",
label: "操作",
render: (item) => <ProductActions item={item} page={page} />,
width: "minmax(96px, 0.45fr)",
},
];
}
function ProductStatusSwitch({ item, page }) {
const checked = Boolean(item.enabled);
const disabled = !page.abilities.canUpdateProduct || page.loadingAction === `status:${item.productId}`;
return (
<AdminSwitch
checked={checked}
checkedLabel="上架"
disabled={disabled}
inputProps={{ "aria-label": checked ? "下架内购" : "上架内购" }}
uncheckedLabel="下架"
onChange={(event) => page.toggleProductEnabled(item, event.target.checked)}
/>
);
}
function ProductActions({ item, page }) {
const deleting = page.loadingAction === `delete:${item.productId}`;
return (
<AdminRowActions>
{page.abilities.canUpdateProduct ? (
<AdminActionIconButton
disabled={Boolean(page.loadingAction)}
label="编辑"
onClick={() => page.openEdit(item)}
>
<EditOutlined fontSize="small" />
</AdminActionIconButton>
) : null}
{page.abilities.canDeleteProduct ? (
<AdminActionIconButton
disabled={deleting || Boolean(page.loadingAction)}
label="删除"
onClick={() => page.removeProduct(item)}
>
<DeleteOutlineOutlined fontSize="small" />
</AdminActionIconButton>
) : null}
</AdminRowActions>
);
}
function Stack({ primary, secondary }) {
return (
<div className="cell-stack">
<span>{primary || "-"}</span>
<span className="muted">{secondary || "-"}</span>
</div>
);
}
function platformLabel(value) {
if (value === "web") {
return "Web";
}
if (value === "android") {
return "Android";
}
if (value === "ios") {
return "iOS";
}
return value || "-";
}
function audienceLabel(value) {
if (value === "coin_seller") {
return "币商";
}
if (value === "normal") {
return "普通用户";
}
return value || "普通用户";
}
function regionLabelMap(options) {
return options.reduce((labels, option) => {
labels[String(option.value)] = option.name || option.label;
return labels;
}, {});
}
function regionListLabel(labels, regionIds = []) {
if (!regionIds.length) {
return "-";
}
return regionIds.map((regionId) => labels[String(regionId)] || `区域 ${regionId}`).join("、");
}
function formatNumber(value) {
const numberValue = Number(value || 0);
return Number.isFinite(numberValue) ? numberValue.toLocaleString() : "-";
}
function formatMicroAmount(value) {
const numberValue = Number(value || 0);
if (!Number.isFinite(numberValue) || numberValue <= 0) {
return "0";
}
return String(numberValue / 1_000_000).replace(/\.?0+$/, "");
}