feat: harden platform workflows and UI governance
This commit is contained in:
@@ -2,8 +2,8 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Building2, DollarSign, Plus, Trash2, TrendingDown, TrendingUp } from 'lucide-react';
|
||||
import { adminApi, type TenantManagementRow } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, Input, Modal, Select, Table, Tag, Textarea, type TableColumn } from '@/components/ui';
|
||||
import { formatCents, isValidMoneyInput, yuanToMoneyUnits } from '@/utils/currency';
|
||||
import { Breadcrumb, Button, Input, ManualRechargeDialog, Modal, Select, Table, Tag, type TableColumn } from '@/components/ui';
|
||||
import { formatCents } from '@/utils/currency';
|
||||
|
||||
type AdminCustomersPageProps = {
|
||||
basePath?: string;
|
||||
@@ -11,11 +11,6 @@ type AdminCustomersPageProps = {
|
||||
|
||||
type CustomerRow = TenantManagementRow;
|
||||
|
||||
type RechargeForm = {
|
||||
amount: string;
|
||||
remark: string;
|
||||
};
|
||||
|
||||
function ConfirmModal({ message, onCancel, onConfirm }: { message: string; onCancel: () => void; onConfirm: () => void }) {
|
||||
return (
|
||||
<Modal footer={<><Button onClick={onCancel} variant="ghost">取消</Button><Button onClick={onConfirm}>确认</Button></>} onClose={onCancel} open title="操作确认">
|
||||
@@ -24,13 +19,6 @@ function ConfirmModal({ message, onCancel, onConfirm }: { message: string; onCan
|
||||
);
|
||||
}
|
||||
|
||||
function emptyRechargeForm(): RechargeForm {
|
||||
return {
|
||||
amount: '',
|
||||
remark: '',
|
||||
};
|
||||
}
|
||||
|
||||
export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCustomersPageProps) {
|
||||
const navigate = useNavigate();
|
||||
const [records, setRecords] = useState<CustomerRow[]>([]);
|
||||
@@ -39,9 +27,6 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
|
||||
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);
|
||||
const [rechargeError, setRechargeError] = useState('');
|
||||
const [recharging, setRecharging] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
function loadData() {
|
||||
@@ -108,37 +93,6 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
|
||||
|
||||
function openRechargeModal(record: CustomerRow) {
|
||||
setRechargeTarget(record);
|
||||
setRechargeForm(emptyRechargeForm());
|
||||
setRechargeError('');
|
||||
}
|
||||
|
||||
function updateRechargeForm<K extends keyof RechargeForm>(key: K, value: RechargeForm[K]) {
|
||||
setRechargeForm((current) => ({ ...current, [key]: value }));
|
||||
setRechargeError('');
|
||||
}
|
||||
|
||||
async function submitRecharge() {
|
||||
if (!rechargeTarget) return;
|
||||
const amount = Number(rechargeForm.amount);
|
||||
if (!Number.isFinite(amount) || !isValidMoneyInput(rechargeForm.amount, { allowNegative: true, allowZero: false })) {
|
||||
setRechargeError('请填写非 0 的充值金额,支持负数冲正');
|
||||
return;
|
||||
}
|
||||
setRecharging(true);
|
||||
try {
|
||||
await adminApi.createManualRecharge({
|
||||
tenantId: rechargeTarget.id,
|
||||
amountCents: yuanToMoneyUnits(rechargeForm.amount),
|
||||
remark: rechargeForm.remark,
|
||||
});
|
||||
setRechargeTarget(null);
|
||||
setRechargeForm(emptyRechargeForm());
|
||||
await loadData();
|
||||
} catch (failure) {
|
||||
setRechargeError(failure instanceof Error ? failure.message : '企业充值失败');
|
||||
} finally {
|
||||
setRecharging(false);
|
||||
}
|
||||
}
|
||||
|
||||
function submitConfirmAction() {
|
||||
@@ -191,28 +145,19 @@ export function AdminCustomersPage({ basePath = '/admin/customers' }: AdminCusto
|
||||
onConfirm={submitConfirmAction}
|
||||
/>
|
||||
) : null}
|
||||
{rechargeTarget ? (
|
||||
<Modal
|
||||
footer={(
|
||||
<>
|
||||
<Button disabled={recharging} onClick={() => setRechargeTarget(null)} variant="ghost">取消</Button>
|
||||
<Button disabled={recharging} onClick={() => { void submitRecharge(); }}>{recharging ? '充值中...' : '确认充值'}</Button>
|
||||
</>
|
||||
)}
|
||||
onClose={() => setRechargeTarget(null)}
|
||||
open
|
||||
size="md"
|
||||
title="企业人工充值"
|
||||
>
|
||||
<div className="admin-system-modal-form">
|
||||
<Input disabled label="企业名称" value={rechargeTarget.name} />
|
||||
<Input disabled label="当前余额" prefix="¥" value={formatCents(rechargeTarget.account?.balanceCents ?? 0)} />
|
||||
<Input label="充值金额" onChange={(event) => updateRechargeForm('amount', event.target.value)} prefix="¥" required step="0.0001" type="number" value={rechargeForm.amount} />
|
||||
<Textarea className="admin-system-modal-form__wide" label="充值备注" onChange={(event) => updateRechargeForm('remark', event.target.value)} rows={4} value={rechargeForm.remark} />
|
||||
</div>
|
||||
{rechargeError ? <p className="form-error">{rechargeError}</p> : null}
|
||||
</Modal>
|
||||
) : null}
|
||||
<ManualRechargeDialog
|
||||
initialTargetId={rechargeTarget?.id}
|
||||
lockTarget
|
||||
onClose={() => setRechargeTarget(null)}
|
||||
onCompleted={loadData}
|
||||
open={Boolean(rechargeTarget)}
|
||||
targets={rechargeTarget ? [{
|
||||
id: rechargeTarget.id,
|
||||
name: rechargeTarget.name,
|
||||
code: rechargeTarget.code,
|
||||
balanceCents: rechargeTarget.account?.balanceCents ?? 0,
|
||||
}] : []}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user