From 477f9cc7a9db605629e94a54580d46dfcda2012f Mon Sep 17 00:00:00 2001 From: zzhyyyyy <2685922758@qq.com> Date: Tue, 30 Jun 2026 11:43:54 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E7=8E=B0=E9=87=91=E5=BC=B9=E7=AA=97:?= =?UTF-8?q?=E7=9B=AE=E6=A0=87=E8=B4=A6=E6=88=B7=E9=80=89=E6=8B=A9=20+=20?= =?UTF-8?q?=E5=9B=9B=E9=A1=B9=E5=BF=85=E5=A1=AB=E6=A0=A1=E9=AA=8C=20+=20?= =?UTF-8?q?=E9=82=80=E8=AF=B7=E4=BD=99=E9=A2=9D=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增「目标账户」单选(金币兑现金账户 / 邀请账户),默认不选、必选 - 操作方式改为必选无默认(原默认「增减」),进来需手动选 - 四项(目标账户/操作方式/现金变动/原因)全部必填必选:标题带红星 *, 点确定时缺项 → 字段下方红字报错并阻断提交 - 提交携带 account;弹窗顶部余额行加显「邀请奖励金」(UserOverview 新字段) Co-Authored-By: Claude Opus 4.8 --- src/components/AdjustBalanceModals.tsx | 65 +++++++++++++++++--------- src/lib/types.ts | 1 + 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/components/AdjustBalanceModals.tsx b/src/components/AdjustBalanceModals.tsx index 8c287b7..c9e0d26 100644 --- a/src/components/AdjustBalanceModals.tsx +++ b/src/components/AdjustBalanceModals.tsx @@ -3,7 +3,7 @@ // 调金币 / 调现金弹窗(受控)。列表页与用户详情页共用,避免两处各写一套。 // 每个弹窗自带「增减 / 设为指定值」两种模式;打开时拉一次 360 概览展示当前余额。 import { useEffect, useState } from 'react'; -import { App, Form, Input, InputNumber, Modal, Segmented } from 'antd'; +import { App, Form, Input, InputNumber, Modal, Radio, Segmented } from 'antd'; import { api, errMsg } from '@/lib/api'; import { yuan } from '@/lib/format'; import type { UserOverview } from '@/lib/types'; @@ -17,15 +17,19 @@ interface ModalProps { onDone?: () => void; // 调整成功后回调(刷新余额/列表) } -const balanceLine = (balances: { coin: number; cashCents: number } | null) => ( +type Balances = { coin: number; cashCents: number; inviteCashCents: number }; + +const balanceLine = (balances: Balances | null) => (

- 当前余额:金币 {balances ? balances.coin : '…'} | 现金 {balances ? yuan(balances.cashCents) : '…'} + 当前余额:金币 {balances ? balances.coin : '…'} | 现金(金币兑现金){' '} + {balances ? yuan(balances.cashCents) : '…'} | 邀请奖励金{' '} + {balances ? yuan(balances.inviteCashCents) : '…'}

); // 弹窗打开时拉一次该用户余额;用户切换/关闭时丢弃过期响应(alive 标记防竞态)。 function useBalances(userId: number | undefined, open: boolean) { - const [balances, setBalances] = useState<{ coin: number; cashCents: number } | null>(null); + const [balances, setBalances] = useState(null); useEffect(() => { if (!open || userId == null) { setBalances(null); @@ -36,7 +40,12 @@ function useBalances(userId: number | undefined, open: boolean) { api .get(`/admin/api/users/${userId}`) .then((r) => { - if (alive) setBalances({ coin: r.data.coin_balance, cashCents: r.data.cash_balance_cents }); + if (alive) + setBalances({ + coin: r.data.coin_balance, + cashCents: r.data.cash_balance_cents, + inviteCashCents: r.data.invite_cash_balance_cents, + }); }) .catch(() => {}); return () => { @@ -113,39 +122,39 @@ export function AdjustCoinModal({ user, open, onClose, onDone }: ModalProps) { export function AdjustCashModal({ user, open, onClose, onDone }: ModalProps) { const { message } = App.useApp(); const [form] = Form.useForm(); - const [mode, setMode] = useState<'delta' | 'set'>('delta'); + // 操作方式改为表单必选项(无默认、手动选);用 watch 驱动金额栏文案/最小值 + const mode = Form.useWatch('mode', form) as 'delta' | 'set' | undefined; const balances = useBalances(user?.id, open); useEffect(() => { - if (open) { - setMode('delta'); - form.resetFields(); - } + if (open) form.resetFields(); // 目标账户 / 操作方式 都重置为「未选」 }, [open, form]); // 输入元,×100 转分后调后端(主要给无现金用户发钱直接测试提现) const submit = async () => { if (!user) return; - const v = await form.validateFields(); + const v = await form.validateFields(); // 任一必填/必选缺失 → 该项下方红字 + 阻断提交 const amountCents = Math.round(Number(v.amount_yuan) * 100); - if (mode === 'delta' && amountCents === 0) { + if (v.mode === 'delta' && amountCents === 0) { message.warning('金额不能为 0(且不小于 1 分)'); return; } - if (mode === 'set' && amountCents < 0) { + if (v.mode === 'set' && amountCents < 0) { message.warning('目标金额不能为负'); return; } + const acctName = v.account === 'invite_cash' ? '邀请奖励金' : '现金'; try { await api.post(`/admin/api/users/${user.id}/cash`, { - mode, + account: v.account, + mode: v.mode, amount_cents: amountCents, reason: v.reason, }); message.success( - mode === 'set' - ? `已设为 ¥${(amountCents / 100).toFixed(2)} 现金` - : `已${amountCents > 0 ? '发放' : '扣减'} ¥${(Math.abs(amountCents) / 100).toFixed(2)} 现金`, + v.mode === 'set' + ? `已设为 ¥${(amountCents / 100).toFixed(2)} ${acctName}` + : `已${amountCents > 0 ? '发放' : '扣减'} ¥${(Math.abs(amountCents) / 100).toFixed(2)} ${acctName}`, ); onClose(); onDone?.(); @@ -164,10 +173,20 @@ export function AdjustCashModal({ user, open, onClose, onDone }: ModalProps) { > {balanceLine(balances)}
- - setMode(v as 'delta' | 'set')} + + + + + @@ -194,7 +213,7 @@ export function AdjustCashModal({ user, open, onClose, onDone }: ModalProps) { min={mode === 'set' ? 0 : undefined} /> - + diff --git a/src/lib/types.ts b/src/lib/types.ts index 5e99d1d..c3a95c9 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -81,6 +81,7 @@ export interface UserOverview { user: UserListItem; coin_balance: number; cash_balance_cents: number; + invite_cash_balance_cents: number; // 邀请奖励金余额(与 cash_balance_cents 物理隔离) total_coin_earned: number; comparison_total: number; comparison_success: number;