fix: simplify balance billing and govern operation logs

This commit is contained in:
hectorzhao
2026-07-14 17:40:32 +08:00
parent f35691f185
commit 28dad93e3e
40 changed files with 740 additions and 395 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ export function AdminCustomerDetailPage() {
<div className="dashboard-grid enterprise-summary-grid">
<div className="surface mini-status-card"><Server size={22} /><div><span></span><strong>{tenant?.code ?? '-'}</strong><small>{tenant?.status ?? '-'}</small></div></div>
<div className="surface mini-status-card"><MessageSquare size={22} /><div><span></span><strong>{(account?.smsUnits ?? 0).toLocaleString('zh-CN')}</strong><small></small></div></div>
<div className="surface mini-status-card"><MessageSquare size={22} /><div><span></span><strong></strong><small></small></div></div>
<div className="surface mini-status-card"><FileText size={22} /><div><span></span><strong>¥{formatCents(account?.balanceCents)}</strong><small></small></div></div>
</div>
+5 -10
View File
@@ -36,10 +36,9 @@ function emptyRechargeForm(): RechargeForm {
export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCustomersPageProps) {
const navigate = useNavigate();
const [records, setRecords] = useState<CustomerRow[]>([]);
const [queryId, setQueryId] = useState('');
const [queryName, setQueryName] = useState('');
const [queryStatus, setQueryStatus] = useState('all');
const [filters, setFilters] = useState({ id: '', name: '', status: 'all' });
const [filters, setFilters] = useState({ name: '', status: 'all' });
const [confirmAction, setConfirmAction] = useState<{ type: 'toggle' | 'delete'; record: CustomerRow } | null>(null);
const [rechargeTarget, setRechargeTarget] = useState<CustomerRow | null>(null);
const [rechargeForm, setRechargeForm] = useState<RechargeForm>(emptyRechargeForm);
@@ -61,10 +60,9 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
}, []);
const filteredRecords = useMemo(() => records.filter((record) => {
const matchId = filters.id ? record.id.includes(filters.id) : true;
const matchName = filters.name ? record.name.includes(filters.name) : true;
const matchStatus = filters.status === 'all' ? true : record.status === filters.status;
return matchId && matchName && matchStatus;
return matchName && matchStatus;
}), [filters, records]);
const activeCount = records.filter((record) => record.status === 'active').length;
@@ -72,7 +70,6 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
const totalBalance = records.reduce((sum, record) => sum + (record.account?.balanceCents ?? 0), 0);
const columns: Array<TableColumn<CustomerRow>> = [
{ key: 'id', title: '企业ID', width: '160px', render: (record) => <span className="table-mono-id">{record.id}</span> },
{ key: 'name', title: '企业名称', width: '260px', render: (record) => <strong className="table-strong-text">{record.name}</strong> },
{
key: 'balance',
@@ -89,8 +86,8 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
);
},
},
{ key: 'overdraftLimit', title: '透支限额', width: '150px', align: 'right', render: (record) => `¥${formatCents(record.account?.creditCents ?? 0)}` },
{ key: 'todaySpend', title: '今日消费', width: '150px', align: 'right', render: (record) => `¥${formatCents(record.todaySpendCents)}` },
{ key: 'todayRefund', title: '今日返还', width: '150px', align: 'right', render: (record) => `¥${formatCents(record.todayRefundCents)}` },
{ key: 'status', title: '企业状态', width: '130px', render: (record) => <Tag tone={record.status === 'active' ? 'success' : 'warning'}>{record.status === 'active' ? '正常' : '已禁用'}</Tag> },
{
key: 'actions',
@@ -133,7 +130,6 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
await adminApi.createManualRecharge({
tenantId: rechargeTarget.id,
amountCents: Math.round(amount * 100),
smsUnits: 0,
remark: [rechargeForm.operator, rechargeForm.remark].filter(Boolean).join(' / '),
});
setRechargeTarget(null);
@@ -175,12 +171,11 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
<div className="surface ui-query-panel">
<h2></h2>
<div className="ui-query-panel__grid enterprise-query-grid">
<Input label="企业ID" onChange={(event) => setQueryId(event.target.value)} placeholder="请输入企业ID" value={queryId} />
<Input label="企业名称" onChange={(event) => setQueryName(event.target.value)} placeholder="请输入企业名称" value={queryName} />
<Select label="企业状态" onChange={(event) => setQueryStatus(event.target.value)} options={[{ label: '全部状态', value: 'all' }, { label: '正常', value: 'active' }, { label: '禁用', value: 'disabled' }]} value={queryStatus} />
<div className="enterprise-query-actions">
<Button onClick={() => setFilters({ id: queryId, name: queryName, status: queryStatus })} variant="secondary"></Button>
<Button onClick={() => { setQueryId(''); setQueryName(''); setQueryStatus('all'); setFilters({ id: '', name: '', status: 'all' }); }} variant="ghost"></Button>
<Button onClick={() => setFilters({ name: queryName, status: queryStatus })} variant="secondary"></Button>
<Button onClick={() => { setQueryName(''); setQueryStatus('all'); setFilters({ name: '', status: 'all' }); }} variant="ghost"></Button>
</div>
</div>
</div>
+1 -1
View File
@@ -69,7 +69,7 @@ export function AdminHome() {
const todaySpend = Math.abs(dashboard?.recentRecharges
.filter((item) => item.tenantId === account.tenantId)
.reduce((sum, item) => sum + item.amountCents, 0) ?? 0) / 100;
const availableBalance = (account.balanceCents + account.creditCents) / 100;
const availableBalance = account.balanceCents / 100;
return {
id: account.tenantId,
enterprise: account.tenant?.name ?? account.tenantId,
+7 -11
View File
@@ -8,7 +8,6 @@ import { formatAmount } from '@/utils/currency';
type ManualRechargeForm = {
tenantId: string;
amount: string;
smsUnits: string;
operator: string;
remark: string;
};
@@ -31,7 +30,7 @@ export function AdminRechargeRecordsPage() {
const [enterpriseKeyword, setEnterpriseKeyword] = useState('');
const [dateRange, setDateRange] = useState<DateRangeValue>({});
const [manualOpen, setManualOpen] = useState(false);
const [form, setForm] = useState<ManualRechargeForm>({ tenantId: '', amount: '', smsUnits: '0', operator: '运营', remark: '' });
const [form, setForm] = useState<ManualRechargeForm>({ tenantId: '', amount: '', operator: '运营', remark: '' });
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
@@ -93,9 +92,8 @@ export function AdminRechargeRecordsPage() {
async function submitManualRecharge() {
const amount = Number(form.amount);
const smsUnits = Number(form.smsUnits || 0);
if (!form.tenantId || !Number.isFinite(amount) || !Number.isFinite(smsUnits) || (amount === 0 && smsUnits === 0)) {
setManualError('请填写非 0 的充值金额或短信条数;金额支持负数冲正。');
if (!form.tenantId || !Number.isFinite(amount) || amount === 0) {
setManualError('请填写非 0 的充值金额;金额支持负数冲正。');
return;
}
setSubmitting(true);
@@ -104,12 +102,11 @@ export function AdminRechargeRecordsPage() {
await adminApi.createManualRecharge({
tenantId: form.tenantId,
amountCents: Math.round(amount * 100),
smsUnits,
remark: [form.operator, form.remark].filter(Boolean).join(' / '),
});
await loadData();
setManualOpen(false);
setForm({ tenantId: tenants[0]?.id ?? '', amount: '', smsUnits: '0', operator: '运营', remark: '' });
setForm({ tenantId: tenants[0]?.id ?? '', amount: '', operator: '运营', remark: '' });
} catch (failure) {
setManualError(failure instanceof Error ? failure.message : '人工充值失败');
} finally {
@@ -151,11 +148,11 @@ export function AdminRechargeRecordsPage() {
</thead>
<tbody>
{error ? (
<tr><td className="ui-table__empty" colSpan={7}>{error}</td></tr>
<tr><td className="ui-table__empty" colSpan={6}>{error}</td></tr>
) : loading ? (
<tr><td className="ui-table__empty" colSpan={7}>...</td></tr>
<tr><td className="ui-table__empty" colSpan={6}>...</td></tr>
) : filteredRows.length === 0 ? (
<tr><td className="ui-table__empty" colSpan={7}></td></tr>
<tr><td className="ui-table__empty" colSpan={6}></td></tr>
) : visibleRows.map((record) => {
const tenantName = record.tenant?.name ?? tenants.find((tenant) => tenant.id === record.tenantId)?.name ?? record.tenantId;
return (
@@ -207,7 +204,6 @@ export function AdminRechargeRecordsPage() {
value={form.tenantId}
/>
<Input label="充值金额" onChange={(event) => updateForm('amount', event.target.value)} prefix="¥" required type="number" value={form.amount} />
<Input label="短信条数" onChange={(event) => updateForm('smsUnits', event.target.value)} type="number" value={form.smsUnits} />
<Input label="操作人" onChange={(event) => updateForm('operator', event.target.value)} required value={form.operator} />
<Textarea className="admin-system-modal-form__wide" label="充值备注" onChange={(event) => updateForm('remark', event.target.value)} rows={4} value={form.remark} />
</div>