133 lines
5.7 KiB
JavaScript
133 lines
5.7 KiB
JavaScript
import Dialog from "@mui/material/Dialog";
|
||
import DialogActions from "@mui/material/DialogActions";
|
||
import DialogContent from "@mui/material/DialogContent";
|
||
import DialogTitle from "@mui/material/DialogTitle";
|
||
import TextField from "@mui/material/TextField";
|
||
import { useState } from "react";
|
||
import { Button } from "@/shared/ui/Button.jsx";
|
||
import { formatNumber } from "../format.js";
|
||
|
||
export function LuckyGiftPoolAdjustmentDialog({ adjustmentId, direction, onClose, onSubmit, pool, submitting }) {
|
||
const [amount, setAmount] = useState("");
|
||
const [reason, setReason] = useState("");
|
||
const [errors, setErrors] = useState({});
|
||
const debit = direction === "out";
|
||
const appCode = pool.app_code;
|
||
const poolID = pool.pool_id;
|
||
const strategyVersion = pool.strategy_version;
|
||
const availableBalance = Number(pool.available_balance || 0);
|
||
const title = debit ? "扣减奖池水位" : "添加奖池水位";
|
||
|
||
const submit = (event) => {
|
||
event.preventDefault();
|
||
const nextErrors = validateAdjustment({ amount, availableBalance, debit, reason });
|
||
setErrors(nextErrors);
|
||
if (Object.keys(nextErrors).length) {
|
||
return;
|
||
}
|
||
|
||
// adjustmentId 在打开弹窗时创建,此处只原样透传。请求失败后组件不会卸载,因此用户修正或直接重试时
|
||
// 仍使用同一幂等键,网络超时场景不会把已成功的资金动作重复执行。
|
||
onSubmit({
|
||
adjustmentId,
|
||
amountCoins: Number(amount),
|
||
appCode,
|
||
direction,
|
||
poolId: poolID,
|
||
reason: reason.trim(),
|
||
strategyVersion,
|
||
});
|
||
};
|
||
|
||
return (
|
||
<Dialog
|
||
fullWidth
|
||
maxWidth="sm"
|
||
open
|
||
aria-labelledby="lucky-pool-adjustment-title"
|
||
onClose={submitting ? undefined : onClose}
|
||
>
|
||
<form className="ops-pool-adjustment-form" onSubmit={submit}>
|
||
<DialogTitle id="lucky-pool-adjustment-title">{title}</DialogTitle>
|
||
<DialogContent dividers className="ops-pool-adjustment-content">
|
||
<div className="ops-config-metrics ops-pool-adjustment-metrics">
|
||
<PoolMetric label="应用" value={appCode} />
|
||
<PoolMetric label="奖池" value={poolID} />
|
||
<PoolMetric label="策略" value={strategyVersion} />
|
||
<PoolMetric label="当前余额" value={formatNumber(pool.balance)} />
|
||
<PoolMetric label="保底线" value={formatNumber(pool.reserve_floor)} />
|
||
<PoolMetric label="可用余额" value={formatNumber(availableBalance)} />
|
||
</div>
|
||
{pool.materialized === false ? (
|
||
<div className="ops-pool-adjustment-note" role="status">
|
||
当前为默认水位,提交后将物化为独立策略奖池。
|
||
</div>
|
||
) : null}
|
||
<TextField
|
||
autoFocus
|
||
disabled={submitting}
|
||
error={Boolean(errors.amount)}
|
||
fullWidth
|
||
helperText={errors.amount || (debit ? `最多可扣减 ${formatNumber(availableBalance)} 金币` : "")}
|
||
label="调整金币"
|
||
required
|
||
slotProps={{ htmlInput: { inputMode: "numeric", pattern: "[0-9]*" } }}
|
||
value={amount}
|
||
onChange={(event) => {
|
||
setAmount(event.target.value);
|
||
setErrors((current) => ({ ...current, amount: "" }));
|
||
}}
|
||
/>
|
||
<TextField
|
||
disabled={submitting}
|
||
error={Boolean(errors.reason)}
|
||
fullWidth
|
||
helperText={errors.reason}
|
||
label="调整原因"
|
||
multiline
|
||
required
|
||
rows={3}
|
||
slotProps={{ htmlInput: { maxLength: 255 } }}
|
||
value={reason}
|
||
onChange={(event) => {
|
||
setReason(event.target.value);
|
||
setErrors((current) => ({ ...current, reason: "" }));
|
||
}}
|
||
/>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Button disabled={submitting} onClick={onClose}>
|
||
取消
|
||
</Button>
|
||
<Button disabled={submitting} type="submit" variant={debit ? "danger" : "primary"}>
|
||
{debit ? "确认扣减" : "确认添加"}
|
||
</Button>
|
||
</DialogActions>
|
||
</form>
|
||
</Dialog>
|
||
);
|
||
}
|
||
|
||
function PoolMetric({ label, value }) {
|
||
return (
|
||
<div className="ops-config-metric">
|
||
<span>{label}</span>
|
||
<strong>{value || "-"}</strong>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function validateAdjustment({ amount, availableBalance, debit, reason }) {
|
||
const errors = {};
|
||
const rawAmount = String(amount || "").trim();
|
||
if (!/^[1-9]\d*$/.test(rawAmount) || !Number.isSafeInteger(Number(rawAmount))) {
|
||
errors.amount = "请输入大于 0 的整数金币数量";
|
||
} else if (debit && Number(rawAmount) > availableBalance) {
|
||
errors.amount = `扣减不能超过可用余额 ${formatNumber(availableBalance)}`;
|
||
}
|
||
if (!String(reason || "").trim()) {
|
||
errors.reason = "请输入调整原因";
|
||
}
|
||
return errors;
|
||
}
|