'use client'; // 调金币 / 调现金弹窗(受控)。列表页与用户详情页共用,避免两处各写一套。 // 每个弹窗自带「增减 / 设为指定值」两种模式;打开时拉一次 360 概览展示当前余额。 import { useEffect, useState } from 'react'; 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'; export type AdjustTargetUser = { id: number; phone: string }; interface ModalProps { user: AdjustTargetUser | null; open: boolean; onClose: () => void; onDone?: () => void; // 调整成功后回调(刷新余额/列表) } type Balances = { coin: number; cashCents: number; inviteCashCents: number }; const balanceLine = (balances: Balances | null) => (

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

); // 弹窗打开时拉一次该用户余额;用户切换/关闭时丢弃过期响应(alive 标记防竞态)。 function useBalances(userId: number | undefined, open: boolean) { const [balances, setBalances] = useState(null); useEffect(() => { if (!open || userId == null) { setBalances(null); return; } let alive = true; setBalances(null); api .get(`/admin/api/users/${userId}`) .then((r) => { if (alive) setBalances({ coin: r.data.coin_balance, cashCents: r.data.cash_balance_cents, inviteCashCents: r.data.invite_cash_balance_cents, }); }) .catch(() => {}); return () => { alive = false; }; }, [userId, open]); return balances; } export function AdjustCoinModal({ user, open, onClose, onDone }: ModalProps) { const { message } = App.useApp(); const [form] = Form.useForm(); const [mode, setMode] = useState<'delta' | 'set'>('delta'); const balances = useBalances(user?.id, open); useEffect(() => { if (open) { setMode('delta'); form.resetFields(); } }, [open, form]); const submit = async () => { if (!user) return; const v = await form.validateFields(); try { await api.post(`/admin/api/users/${user.id}/coins`, { mode, ...v }); message.success(mode === 'set' ? '已设置金币' : '已调整金币'); onClose(); onDone?.(); } catch (e) { message.error(errMsg(e)); } }; return ( {balanceLine(balances)}
setMode(v as 'delta' | 'set')} options={[ { label: '增减', value: 'delta' }, { label: '设为指定值', value: 'set' }, ]} />
); } export function AdjustCashModal({ user, open, onClose, onDone }: ModalProps) { const { message } = App.useApp(); const [form] = Form.useForm(); // 操作方式改为表单必选项(无默认、手动选);用 watch 驱动金额栏文案/最小值 const mode = Form.useWatch('mode', form) as 'delta' | 'set' | undefined; const balances = useBalances(user?.id, open); useEffect(() => { if (open) form.resetFields(); // 目标账户 / 操作方式 都重置为「未选」 }, [open, form]); // 输入元,×100 转分后调后端(主要给无现金用户发钱直接测试提现) const submit = async () => { if (!user) return; const v = await form.validateFields(); // 任一必填/必选缺失 → 该项下方红字 + 阻断提交 const amountCents = Math.round(Number(v.amount_yuan) * 100); if (v.mode === 'delta' && amountCents === 0) { message.warning('金额不能为 0(且不小于 1 分)'); return; } 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`, { account: v.account, mode: v.mode, amount_cents: amountCents, reason: v.reason, }); message.success( v.mode === 'set' ? `已设为 ¥${(amountCents / 100).toFixed(2)} ${acctName}` : `已${amountCents > 0 ? '发放' : '扣减'} ¥${(Math.abs(amountCents) / 100).toFixed(2)} ${acctName}`, ); onClose(); onDone?.(); } catch (e) { message.error(errMsg(e)); } }; return ( {balanceLine(balances)}
); }