可配置
This commit is contained in:
parent
0789e02c6a
commit
57231bc95a
@ -30,6 +30,7 @@ test("gift diamond ratio APIs use operations paths", async () => {
|
||||
await updateGiftDiamondRatios({
|
||||
regionId: 1001,
|
||||
ratios: { lucky: "40.00", normal: "30.00", super_lucky: "50.00" },
|
||||
returnCoinRatios: { lucky: "10.00", normal: "30.00", super_lucky: "1.00" },
|
||||
});
|
||||
|
||||
const [getUrl, getInit] = vi.mocked(fetch).mock.calls[0];
|
||||
@ -43,6 +44,7 @@ test("gift diamond ratio APIs use operations paths", async () => {
|
||||
expect(JSON.parse(String(putInit?.body))).toMatchObject({
|
||||
regionId: 1001,
|
||||
ratios: { lucky: "40.00", normal: "30.00", super_lucky: "50.00" },
|
||||
returnCoinRatios: { lucky: "10.00", normal: "30.00", super_lucky: "1.00" },
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ export interface GiftDiamondRatioItem {
|
||||
effectiveRegionId: number;
|
||||
giftTypeCode: string;
|
||||
ratioPercent: string;
|
||||
returnCoinRatioPercent: string;
|
||||
regionId: number;
|
||||
status: string;
|
||||
updatedAtMs?: number;
|
||||
@ -226,6 +227,7 @@ export function getGiftDiamondRatios(regionId: string | number): Promise<GiftDia
|
||||
export function updateGiftDiamondRatios(payload: {
|
||||
regionId: number;
|
||||
ratios: Record<string, string>;
|
||||
returnCoinRatios: Record<string, string>;
|
||||
}): Promise<GiftDiamondRatioResponse> {
|
||||
return apiRequest<GiftDiamondRatioResponse, typeof payload>("/v1/admin/operations/gift-diamond-ratios", {
|
||||
body: payload,
|
||||
|
||||
@ -235,6 +235,29 @@
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.ratioTable {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(160px, 0.8fr) repeat(2, minmax(180px, 1fr));
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.ratioTableHeader {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.ratioRow {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.ratioType {
|
||||
min-width: 0;
|
||||
color: var(--text-primary);
|
||||
font-weight: 760;
|
||||
}
|
||||
|
||||
.wheelBody {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
@ -626,10 +649,22 @@
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.ratioGrid {
|
||||
.ratioGrid,
|
||||
.ratioTable {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.ratioTableHeader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ratioRow {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
padding-bottom: var(--space-4);
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
}
|
||||
|
||||
.wheelBody,
|
||||
.wheelConfigGrid,
|
||||
.wheelSummaryGrid,
|
||||
|
||||
@ -21,8 +21,14 @@ const giftTypes = [
|
||||
|
||||
const defaultRatios = {
|
||||
normal: "100.00",
|
||||
lucky: "100.00",
|
||||
super_lucky: "100.00",
|
||||
lucky: "10.00",
|
||||
super_lucky: "1.00",
|
||||
};
|
||||
|
||||
const defaultReturnCoinRatios = {
|
||||
normal: "30.00",
|
||||
lucky: "10.00",
|
||||
super_lucky: "1.00",
|
||||
};
|
||||
|
||||
export function GiftDiamondRatioPage() {
|
||||
@ -30,7 +36,7 @@ export function GiftDiamondRatioPage() {
|
||||
const { showToast } = useToast();
|
||||
const { loadingRegions, regionOptions } = useRegionOptions();
|
||||
const [regionId, setRegionId] = useState("0");
|
||||
const [form, setForm] = useState(defaultRatios);
|
||||
const [form, setForm] = useState({ ratios: defaultRatios, returnCoinRatios: defaultReturnCoinRatios });
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const queryFn = useCallback(() => getGiftDiamondRatios(regionId), [regionId]);
|
||||
@ -40,13 +46,16 @@ export function GiftDiamondRatioPage() {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const next = { ...defaultRatios };
|
||||
const nextRatios = { ...defaultRatios };
|
||||
const nextReturnCoinRatios = { ...defaultReturnCoinRatios };
|
||||
for (const item of query.data?.items || []) {
|
||||
if (Object.prototype.hasOwnProperty.call(next, item.giftTypeCode)) {
|
||||
next[item.giftTypeCode] = item.ratioPercent || "100.00";
|
||||
if (Object.prototype.hasOwnProperty.call(nextRatios, item.giftTypeCode)) {
|
||||
nextRatios[item.giftTypeCode] = item.ratioPercent || defaultRatios[item.giftTypeCode];
|
||||
nextReturnCoinRatios[item.giftTypeCode] =
|
||||
item.returnCoinRatioPercent || defaultReturnCoinRatios[item.giftTypeCode];
|
||||
}
|
||||
}
|
||||
setForm(next);
|
||||
setForm({ ratios: nextRatios, returnCoinRatios: nextReturnCoinRatios });
|
||||
}, [query.data]);
|
||||
|
||||
const selectedRegionLabel = useMemo(() => {
|
||||
@ -56,31 +65,42 @@ export function GiftDiamondRatioPage() {
|
||||
return regionOptions.find((option) => option.value === regionId)?.label || `区域 ${regionId}`;
|
||||
}, [regionId, regionOptions]);
|
||||
|
||||
const changeRatio = (giftType, value) => {
|
||||
setForm((current) => ({ ...current, [giftType]: value }));
|
||||
const changeRatio = (field, giftType, value) => {
|
||||
setForm((current) => ({ ...current, [field]: { ...current[field], [giftType]: value } }));
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
const payload = {};
|
||||
const ratios = {};
|
||||
const returnCoinRatios = {};
|
||||
for (const giftType of giftTypes) {
|
||||
const value = String(form[giftType.value] || "").trim();
|
||||
const value = String(form.ratios[giftType.value] || "").trim();
|
||||
const numeric = Number(value);
|
||||
if (!value || !Number.isFinite(numeric) || numeric < 0 || numeric > 100) {
|
||||
showToast(`${giftType.label}比例必须在 0-100 之间`, "error");
|
||||
showToast(`${giftType.label}主播钻石比例必须在 0-100 之间`, "error");
|
||||
return;
|
||||
}
|
||||
payload[giftType.value] = numeric.toFixed(2);
|
||||
ratios[giftType.value] = numeric.toFixed(2);
|
||||
const returnCoinValue = String(form.returnCoinRatios[giftType.value] || "").trim();
|
||||
const returnCoinNumeric = Number(returnCoinValue);
|
||||
if (!returnCoinValue || !Number.isFinite(returnCoinNumeric) || returnCoinNumeric < 0 || returnCoinNumeric > 100) {
|
||||
showToast(`${giftType.label}返还金币比例必须在 0-100 之间`, "error");
|
||||
return;
|
||||
}
|
||||
returnCoinRatios[giftType.value] = returnCoinNumeric.toFixed(2);
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const result = await updateGiftDiamondRatios({ regionId: Number(regionId), ratios: payload });
|
||||
const next = { ...defaultRatios };
|
||||
const result = await updateGiftDiamondRatios({ regionId: Number(regionId), ratios, returnCoinRatios });
|
||||
const nextRatios = { ...defaultRatios };
|
||||
const nextReturnCoinRatios = { ...defaultReturnCoinRatios };
|
||||
for (const item of result.items || []) {
|
||||
if (Object.prototype.hasOwnProperty.call(next, item.giftTypeCode)) {
|
||||
next[item.giftTypeCode] = item.ratioPercent || "100.00";
|
||||
if (Object.prototype.hasOwnProperty.call(nextRatios, item.giftTypeCode)) {
|
||||
nextRatios[item.giftTypeCode] = item.ratioPercent || defaultRatios[item.giftTypeCode];
|
||||
nextReturnCoinRatios[item.giftTypeCode] =
|
||||
item.returnCoinRatioPercent || defaultReturnCoinRatios[item.giftTypeCode];
|
||||
}
|
||||
}
|
||||
setForm(next);
|
||||
setForm({ ratios: nextRatios, returnCoinRatios: nextReturnCoinRatios });
|
||||
showToast("礼物钻石比例已保存", "success");
|
||||
await query.reload();
|
||||
} catch (err) {
|
||||
@ -124,21 +144,40 @@ export function GiftDiamondRatioPage() {
|
||||
<div className={styles.ratioHeader}>
|
||||
<h2>{selectedRegionLabel}</h2>
|
||||
</div>
|
||||
<div className={styles.ratioGrid}>
|
||||
<div className={styles.ratioTable}>
|
||||
<div className={styles.ratioTableHeader}>礼物类型</div>
|
||||
<div className={styles.ratioTableHeader}>主播钻石比例</div>
|
||||
<div className={styles.ratioTableHeader}>返还金币比例</div>
|
||||
{giftTypes.map((giftType) => (
|
||||
<TextField
|
||||
key={giftType.value}
|
||||
label={giftType.label}
|
||||
type="number"
|
||||
size="small"
|
||||
value={form[giftType.value] || ""}
|
||||
inputProps={{ max: 100, min: 0, step: "0.01" }}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">%</InputAdornment>,
|
||||
}}
|
||||
disabled={!abilities.canUpdate || saving}
|
||||
onChange={(event) => changeRatio(giftType.value, event.target.value)}
|
||||
/>
|
||||
<div key={giftType.value} className={styles.ratioRow}>
|
||||
<div className={styles.ratioType}>{giftType.label}</div>
|
||||
<TextField
|
||||
label="主播钻石比例"
|
||||
type="number"
|
||||
size="small"
|
||||
value={form.ratios[giftType.value] || ""}
|
||||
inputProps={{ max: 100, min: 0, step: "0.01" }}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">%</InputAdornment>,
|
||||
}}
|
||||
disabled={!abilities.canUpdate || saving}
|
||||
onChange={(event) => changeRatio("ratios", giftType.value, event.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
label="返还金币比例"
|
||||
type="number"
|
||||
size="small"
|
||||
value={form.returnCoinRatios[giftType.value] || ""}
|
||||
inputProps={{ max: 100, min: 0, step: "0.01" }}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">%</InputAdornment>,
|
||||
}}
|
||||
disabled={!abilities.canUpdate || saving}
|
||||
onChange={(event) =>
|
||||
changeRatio("returnCoinRatios", giftType.value, event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user