fix: connect operations pages to real backend

This commit is contained in:
hectorzhao
2026-07-02 15:16:34 +08:00
parent ab421cf8a7
commit 321cf716f2
22 changed files with 1255 additions and 429 deletions
+80 -52
View File
@@ -1,33 +1,16 @@
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { ChevronLeft, ChevronRight, Plus, Search } from 'lucide-react';
import { Breadcrumb, Button, DateRangeInput, Input, Modal, Select, Textarea, Tag, type DateRangeValue } from '@/components/ui';
type RechargeRecord = {
id: string;
enterprise: string;
rechargedAt: string;
amount?: number;
balance?: number;
operator?: string;
type: 'manual' | 'package';
remark?: string;
};
import { adminApi, type AccountTransaction, type RechargeOrder, type TenantAccount, type TenantOption } from '@/api/adminApi';
type ManualRechargeForm = {
enterprise: string;
tenantId: string;
amount: string;
smsUnits: string;
operator: string;
remark: string;
};
const rechargeRecordsSeed: RechargeRecord[] = [
{ id: 'RCG202601120001', enterprise: 'XXXX科技有限公司', rechargedAt: '2026-01-12 19:27:19', amount: 1000, balance: 1000, operator: '李XXX', type: 'manual', remark: '线下转账到账' },
{ id: 'RCG202601120002', enterprise: 'XXX公司名字', rechargedAt: '2026-01-12 19:27:19', amount: 500, balance: 5896.25, operator: '张三', type: 'package' },
{ id: 'RCG202601120003', enterprise: 'XXX公司名字XXX公司名字', rechargedAt: '2026-01-12 19:27:19', amount: 192.29, balance: 0, operator: '张三', type: 'manual', remark: '运营补差额' },
{ id: 'RCG202601120004', enterprise: '北京鸣川科技', rechargedAt: '2026-01-12 19:27:19', amount: 2617.09, balance: 0, operator: '李四', type: 'package' },
{ id: 'RCG202601120005', enterprise: '广州麦芒科技', rechargedAt: '2026-01-12 19:27:19', amount: 122, balance: 0, operator: '王五', type: 'manual', remark: '客服人工充值' },
];
function getDate(value: string) {
return value.slice(0, 10);
}
@@ -52,21 +35,54 @@ function RemarkCell({ value }: { value?: string }) {
}
export function AdminRechargeRecordsPage() {
const [records, setRecords] = useState(rechargeRecordsSeed);
const [records, setRecords] = useState<RechargeOrder[]>([]);
const [accounts, setAccounts] = useState<TenantAccount[]>([]);
const [transactions, setTransactions] = useState<AccountTransaction[]>([]);
const [tenants, setTenants] = useState<TenantOption[]>([]);
const [enterpriseKeyword, setEnterpriseKeyword] = useState('');
const [dateRange, setDateRange] = useState<DateRangeValue>({});
const [manualOpen, setManualOpen] = useState(false);
const [form, setForm] = useState<ManualRechargeForm>({ enterprise: '', amount: '', operator: '运营', remark: '' });
const [form, setForm] = useState<ManualRechargeForm>({ tenantId: '', amount: '', smsUnits: '0', operator: '运营', remark: '' });
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
async function loadData() {
setLoading(true);
setError('');
try {
const [nextTenants, nextRecords, nextAccounts, nextTransactions] = await Promise.all([
adminApi.listTenants(),
adminApi.listManualRecharges(),
adminApi.listAccounts(),
adminApi.listTransactions(),
]);
setTenants(nextTenants);
setRecords(nextRecords);
setAccounts(nextAccounts);
setTransactions(nextTransactions);
setForm((current) => ({ ...current, tenantId: current.tenantId || nextTenants[0]?.id || '' }));
} catch (err) {
setError(err instanceof Error ? err.message : '充值记录加载失败');
setRecords([]);
} finally {
setLoading(false);
}
}
useEffect(() => {
void loadData();
}, []);
const filteredRows = useMemo(
() => records.filter((item) => {
const rechargeDate = getDate(item.rechargedAt);
const matchesEnterprise = !enterpriseKeyword || item.enterprise.includes(enterpriseKeyword);
const rechargeDate = getDate(item.paidAt ?? item.createdAt);
const tenantName = item.tenant?.name ?? tenants.find((tenant) => tenant.id === item.tenantId)?.name ?? item.tenantId;
const matchesEnterprise = !enterpriseKeyword || tenantName.includes(enterpriseKeyword);
const matchesStartDate = !dateRange.start || rechargeDate >= dateRange.start;
const matchesEndDate = !dateRange.end || rechargeDate <= dateRange.end;
return matchesEnterprise && matchesStartDate && matchesEndDate;
}),
[dateRange.end, dateRange.start, enterpriseKeyword, records],
[dateRange.end, dateRange.start, enterpriseKeyword, records, tenants],
);
function resetFilters() {
@@ -78,26 +94,21 @@ export function AdminRechargeRecordsPage() {
setForm((current) => ({ ...current, [key]: value }));
}
function submitManualRecharge() {
async function submitManualRecharge() {
const amount = Number(form.amount);
if (!form.enterprise.trim() || !Number.isFinite(amount) || amount <= 0) {
const smsUnits = Number(form.smsUnits || 0);
if (!form.tenantId || !Number.isFinite(amount) || amount <= 0 || !Number.isFinite(smsUnits) || smsUnits < 0) {
return;
}
setRecords((current) => [
{
id: `RCG${Date.now()}`,
enterprise: form.enterprise,
rechargedAt: '2026-07-01 13:58:00',
amount,
balance: amount + 1200,
operator: form.operator,
type: 'manual',
remark: form.remark,
},
...current,
]);
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({ enterprise: '', amount: '', operator: '运营', remark: '' });
setForm({ tenantId: tenants[0]?.id ?? '', amount: '', smsUnits: '0', operator: '运营', remark: '' });
}
return (
@@ -134,17 +145,28 @@ export function AdminRechargeRecordsPage() {
</tr>
</thead>
<tbody>
{filteredRows.map((record) => (
{error ? (
<tr><td className="ui-table__empty" colSpan={7}>{error}</td></tr>
) : loading ? (
<tr><td className="ui-table__empty" colSpan={7}>...</td></tr>
) : filteredRows.length === 0 ? (
<tr><td className="ui-table__empty" colSpan={7}></td></tr>
) : filteredRows.map((record) => {
const account = accounts.find((item) => item.tenantId === record.tenantId);
const transaction = transactions.find((item) => item.relatedId === record.id);
const tenantName = record.tenant?.name ?? tenants.find((tenant) => tenant.id === record.tenantId)?.name ?? record.tenantId;
return (
<tr key={record.id}>
<td><strong>{record.enterprise}</strong></td>
<td>{record.rechargedAt}</td>
<td>{formatAmount(record.amount)}</td>
<td>{formatAmount(record.balance)}</td>
<td><Tag tone={record.type === 'manual' ? 'warning' : 'info'}>{record.type === 'manual' ? '人工充值' : '套餐充值'}</Tag></td>
<td>{record.operator}</td>
<td><RemarkCell value={record.remark} /></td>
<td><strong>{tenantName}</strong></td>
<td>{new Date(record.paidAt ?? record.createdAt).toLocaleString('zh-CN')}</td>
<td>{formatAmount(record.amountCents / 100)}</td>
<td>{formatAmount((transaction?.balanceAfter ?? account?.balanceCents ?? 0) / 100)}</td>
<td><Tag tone="warning"></Tag></td>
<td>{record.operatorId || '运营'}</td>
<td><RemarkCell value={record.remark ?? undefined} /></td>
</tr>
))}
);
})}
</tbody>
</table>
</div>
@@ -177,8 +199,14 @@ export function AdminRechargeRecordsPage() {
title="企业人工充值"
>
<div className="admin-system-modal-form">
<Input label="企业名称" onChange={(event) => updateForm('enterprise', event.target.value)} value={form.enterprise} />
<Select
label="企业名称"
onChange={(event) => updateForm('tenantId', event.target.value)}
options={tenants.map((tenant) => ({ label: tenant.name, value: tenant.id }))}
value={form.tenantId}
/>
<Input label="充值金额" onChange={(event) => updateForm('amount', event.target.value)} prefix="¥" 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)} value={form.operator} />
<Textarea className="admin-system-modal-form__wide" label="充值备注" onChange={(event) => updateForm('remark', event.target.value)} rows={4} value={form.remark} />
</div>