feat(admin-web): add system account transfer management page
- Add system-transfer page with transfer form and order history - Add SystemWithdrawalService for API calls - Add useSystemWithdrawal hooks for React Query integration - Add system-withdrawal types definitions - Add navigation menu item for system transfer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fe8e9a9bb6
commit
b947fe8205
|
|
@ -0,0 +1,690 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
import { Modal, toast, Button } from '@/components/common';
|
||||||
|
import { PageContainer } from '@/components/layout';
|
||||||
|
import { cn } from '@/utils/helpers';
|
||||||
|
import { formatDateTime } from '@/utils/formatters';
|
||||||
|
import {
|
||||||
|
useSystemWithdrawalAccounts,
|
||||||
|
useSystemWithdrawalOrders,
|
||||||
|
useRequestSystemWithdrawal,
|
||||||
|
} from '@/hooks/useSystemWithdrawal';
|
||||||
|
import {
|
||||||
|
SystemWithdrawalOrder,
|
||||||
|
SystemWithdrawalStatus,
|
||||||
|
SystemAccount,
|
||||||
|
getSystemWithdrawalStatusInfo,
|
||||||
|
} from '@/types/system-withdrawal.types';
|
||||||
|
import styles from './system-transfer.module.scss';
|
||||||
|
|
||||||
|
type TabType = 'transfer' | 'orders';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统账户划转管理页面
|
||||||
|
*/
|
||||||
|
export default function SystemTransferPage() {
|
||||||
|
// 当前标签页
|
||||||
|
const [activeTab, setActiveTab] = useState<TabType>('transfer');
|
||||||
|
|
||||||
|
// 筛选状态
|
||||||
|
const [filters, setFilters] = useState({
|
||||||
|
fromAccountSequence: '',
|
||||||
|
toAccountSequence: '',
|
||||||
|
status: '' as SystemWithdrawalStatus | '',
|
||||||
|
page: 1,
|
||||||
|
limit: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表单状态
|
||||||
|
const [selectedAccount, setSelectedAccount] = useState<SystemAccount | null>(null);
|
||||||
|
const [transferForm, setTransferForm] = useState({
|
||||||
|
toAccountSequence: '',
|
||||||
|
amount: '',
|
||||||
|
memo: '',
|
||||||
|
});
|
||||||
|
const [formErrors, setFormErrors] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
|
// 弹窗状态
|
||||||
|
const [viewingOrder, setViewingOrder] = useState<SystemWithdrawalOrder | null>(null);
|
||||||
|
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
|
||||||
|
|
||||||
|
// 数据查询
|
||||||
|
const accountsQuery = useSystemWithdrawalAccounts();
|
||||||
|
const ordersQuery = useSystemWithdrawalOrders({
|
||||||
|
fromAccountSequence: filters.fromAccountSequence || undefined,
|
||||||
|
toAccountSequence: filters.toAccountSequence || undefined,
|
||||||
|
status: filters.status || undefined,
|
||||||
|
page: filters.page,
|
||||||
|
limit: filters.limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mutations
|
||||||
|
const requestMutation = useRequestSystemWithdrawal();
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
const validateForm = (): boolean => {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (!selectedAccount) {
|
||||||
|
errors.fromAccount = '请选择源账户';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transferForm.toAccountSequence) {
|
||||||
|
errors.toAccountSequence = '请输入目标账户序列号';
|
||||||
|
} else if (!/^D\d{11}$/.test(transferForm.toAccountSequence)) {
|
||||||
|
errors.toAccountSequence = '账户序列号格式不正确,应为 D + 11位数字';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transferForm.amount) {
|
||||||
|
errors.amount = '请输入划转金额';
|
||||||
|
} else {
|
||||||
|
const amount = parseFloat(transferForm.amount);
|
||||||
|
if (isNaN(amount) || amount <= 0) {
|
||||||
|
errors.amount = '请输入有效的金额';
|
||||||
|
} else if (amount < 1) {
|
||||||
|
errors.amount = '最小划转金额为 1 USDT';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交划转请求
|
||||||
|
const handleSubmitTransfer = async () => {
|
||||||
|
if (!validateForm() || !selectedAccount) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await requestMutation.mutateAsync({
|
||||||
|
fromAccountSequence: selectedAccount.accountSequence,
|
||||||
|
toAccountSequence: transferForm.toAccountSequence,
|
||||||
|
amount: transferForm.amount,
|
||||||
|
memo: transferForm.memo || undefined,
|
||||||
|
});
|
||||||
|
toast.success('划转请求已提交,等待区块链确认');
|
||||||
|
setConfirmModalOpen(false);
|
||||||
|
// 重置表单
|
||||||
|
setSelectedAccount(null);
|
||||||
|
setTransferForm({ toAccountSequence: '', amount: '', memo: '' });
|
||||||
|
setFormErrors({});
|
||||||
|
// 切换到订单列表查看
|
||||||
|
setActiveTab('orders');
|
||||||
|
} catch (err) {
|
||||||
|
toast.error((err as Error).message || '划转失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开确认弹窗
|
||||||
|
const handleOpenConfirm = () => {
|
||||||
|
if (validateForm()) {
|
||||||
|
setConfirmModalOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = useCallback(() => {
|
||||||
|
setFilters((prev) => ({ ...prev, page: 1 }));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 翻页
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setFilters((prev) => ({ ...prev, page }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染账户选择卡片
|
||||||
|
const renderAccountCards = () => {
|
||||||
|
if (accountsQuery.isLoading) {
|
||||||
|
return <div className={styles.systemTransfer__loading}>加载账户列表...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountsQuery.error) {
|
||||||
|
return (
|
||||||
|
<div className={styles.systemTransfer__error}>
|
||||||
|
<span>{(accountsQuery.error as Error).message || '加载失败'}</span>
|
||||||
|
<Button variant="outline" size="sm" onClick={() => accountsQuery.refetch()}>
|
||||||
|
重试
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accounts = accountsQuery.data || [];
|
||||||
|
if (accounts.length === 0) {
|
||||||
|
return <div className={styles.systemTransfer__empty}>暂无可划转账户</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.systemTransfer__accountCards}>
|
||||||
|
{accounts.map((account) => (
|
||||||
|
<div
|
||||||
|
key={account.accountSequence}
|
||||||
|
className={cn(
|
||||||
|
styles.systemTransfer__accountCard,
|
||||||
|
selectedAccount?.accountSequence === account.accountSequence &&
|
||||||
|
styles['systemTransfer__accountCard--selected']
|
||||||
|
)}
|
||||||
|
onClick={() => setSelectedAccount(account)}
|
||||||
|
>
|
||||||
|
<div className={styles.systemTransfer__accountCardName}>{account.name}</div>
|
||||||
|
<div className={styles.systemTransfer__accountCardSequence}>{account.accountSequence}</div>
|
||||||
|
{account.balance && (
|
||||||
|
<div className={styles.systemTransfer__accountCardBalance}>
|
||||||
|
余额: {parseFloat(account.balance).toFixed(2)} USDT
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染划转表单
|
||||||
|
const renderTransferForm = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.systemTransfer__warning}>
|
||||||
|
从系统账户划转资金到用户账户,资金将通过区块链转账,完成后会自动记录分类账。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style={{ marginBottom: '16px', fontSize: '16px' }}>1. 选择源账户</h3>
|
||||||
|
{renderAccountCards()}
|
||||||
|
{formErrors.fromAccount && (
|
||||||
|
<div className={styles.systemTransfer__formError}>{formErrors.fromAccount}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h3 style={{ marginBottom: '16px', fontSize: '16px' }}>2. 填写划转信息</h3>
|
||||||
|
<div className={styles.systemTransfer__form}>
|
||||||
|
<div className={styles.systemTransfer__formGroup}>
|
||||||
|
<label>目标账户序列号 *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={transferForm.toAccountSequence}
|
||||||
|
onChange={(e) => {
|
||||||
|
setTransferForm({ ...transferForm, toAccountSequence: e.target.value });
|
||||||
|
setFormErrors({ ...formErrors, toAccountSequence: '' });
|
||||||
|
}}
|
||||||
|
placeholder="例如:D25121400005"
|
||||||
|
/>
|
||||||
|
<div className={styles.systemTransfer__formHint}>输入用户的账户序列号(D开头+11位数字)</div>
|
||||||
|
{formErrors.toAccountSequence && (
|
||||||
|
<div className={styles.systemTransfer__formError}>{formErrors.toAccountSequence}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.systemTransfer__formGroup}>
|
||||||
|
<label>划转金额 (USDT) *</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={transferForm.amount}
|
||||||
|
onChange={(e) => {
|
||||||
|
setTransferForm({ ...transferForm, amount: e.target.value });
|
||||||
|
setFormErrors({ ...formErrors, amount: '' });
|
||||||
|
}}
|
||||||
|
placeholder="例如:100"
|
||||||
|
min="1"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
<div className={styles.systemTransfer__formHint}>最小划转金额为 1 USDT</div>
|
||||||
|
{formErrors.amount && (
|
||||||
|
<div className={styles.systemTransfer__formError}>{formErrors.amount}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.systemTransfer__formGroup}>
|
||||||
|
<label>备注 (可选)</label>
|
||||||
|
<textarea
|
||||||
|
value={transferForm.memo}
|
||||||
|
onChange={(e) => setTransferForm({ ...transferForm, memo: e.target.value })}
|
||||||
|
placeholder="请输入划转备注..."
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: '16px' }}>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={handleOpenConfirm}
|
||||||
|
disabled={!selectedAccount || !transferForm.toAccountSequence || !transferForm.amount}
|
||||||
|
>
|
||||||
|
提交划转
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染订单列表
|
||||||
|
const renderOrderList = () => {
|
||||||
|
const { data, isLoading, error, refetch } = ordersQuery;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* 筛选区域 */}
|
||||||
|
<div className={styles.systemTransfer__filters}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className={styles.systemTransfer__input}
|
||||||
|
placeholder="源账户"
|
||||||
|
value={filters.fromAccountSequence}
|
||||||
|
onChange={(e) => setFilters({ ...filters, fromAccountSequence: e.target.value })}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className={styles.systemTransfer__input}
|
||||||
|
placeholder="目标账户"
|
||||||
|
value={filters.toAccountSequence}
|
||||||
|
onChange={(e) => setFilters({ ...filters, toAccountSequence: e.target.value })}
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
className={styles.systemTransfer__select}
|
||||||
|
value={filters.status}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFilters({ ...filters, status: e.target.value as SystemWithdrawalStatus | '' })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="">全部状态</option>
|
||||||
|
<option value="PENDING">待处理</option>
|
||||||
|
<option value="FROZEN">已冻结</option>
|
||||||
|
<option value="BROADCASTED">已广播</option>
|
||||||
|
<option value="CONFIRMED">已确认</option>
|
||||||
|
<option value="FAILED">失败</option>
|
||||||
|
</select>
|
||||||
|
<Button variant="outline" size="sm" onClick={handleSearch}>
|
||||||
|
搜索
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" size="sm" onClick={() => refetch()}>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 列表 */}
|
||||||
|
<div className={styles.systemTransfer__list}>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className={styles.systemTransfer__loading}>加载中...</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className={styles.systemTransfer__error}>
|
||||||
|
<span>{(error as Error).message || '加载失败'}</span>
|
||||||
|
<Button variant="outline" size="sm" onClick={() => refetch()}>
|
||||||
|
重试
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : !data || data.items.length === 0 ? (
|
||||||
|
<div className={styles.systemTransfer__empty}>暂无数据</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* 表格 */}
|
||||||
|
<table className={styles.systemTransfer__table}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>订单号</th>
|
||||||
|
<th>源账户</th>
|
||||||
|
<th>目标账户</th>
|
||||||
|
<th>金额</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>TxHash</th>
|
||||||
|
<th>创建时间</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.items.map((order) => {
|
||||||
|
const statusInfo = getSystemWithdrawalStatusInfo(order.status);
|
||||||
|
return (
|
||||||
|
<tr key={order.orderNo}>
|
||||||
|
<td>
|
||||||
|
<div className={styles.systemTransfer__orderNo}>{order.orderNo}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className={styles.systemTransfer__accountSequence}>
|
||||||
|
{order.fromAccountSequence}
|
||||||
|
</div>
|
||||||
|
<div className={styles.systemTransfer__accountName}>
|
||||||
|
{order.fromAccountName}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className={styles.systemTransfer__accountSequence}>
|
||||||
|
{order.toAccountSequence}
|
||||||
|
</div>
|
||||||
|
{order.toUserName && (
|
||||||
|
<div className={styles.systemTransfer__accountName}>
|
||||||
|
{order.toUserName}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className={styles.systemTransfer__amount}>
|
||||||
|
{parseFloat(order.amount).toFixed(2)} USDT
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
className={styles.systemTransfer__statusTag}
|
||||||
|
style={{ backgroundColor: statusInfo.color, color: 'white' }}
|
||||||
|
>
|
||||||
|
{statusInfo.label}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{order.txHash ? (
|
||||||
|
<div className={styles.systemTransfer__txHash} title={order.txHash}>
|
||||||
|
{order.txHash.slice(0, 10)}...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span style={{ color: '#999' }}>-</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>{formatDateTime(order.createdAt)}</td>
|
||||||
|
<td>
|
||||||
|
<div className={styles.systemTransfer__actions}>
|
||||||
|
<button
|
||||||
|
className={styles.systemTransfer__actionBtn}
|
||||||
|
onClick={() => setViewingOrder(order)}
|
||||||
|
>
|
||||||
|
查看
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{/* 分页 */}
|
||||||
|
{data.total > filters.limit && (
|
||||||
|
<div className={styles.systemTransfer__pagination}>
|
||||||
|
<span>
|
||||||
|
共 {data.total} 条,第 {data.page} / {Math.ceil(data.total / data.limit)} 页
|
||||||
|
</span>
|
||||||
|
<div className={styles.systemTransfer__pageButtons}>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={data.page <= 1}
|
||||||
|
onClick={() => handlePageChange(data.page - 1)}
|
||||||
|
>
|
||||||
|
上一页
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={data.page >= Math.ceil(data.total / data.limit)}
|
||||||
|
onClick={() => handlePageChange(data.page + 1)}
|
||||||
|
>
|
||||||
|
下一页
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer title="系统划转">
|
||||||
|
<div className={styles.systemTransfer}>
|
||||||
|
{/* 页面标题 */}
|
||||||
|
<div className={styles.systemTransfer__header}>
|
||||||
|
<h1 className={styles.systemTransfer__title}>系统账户划转</h1>
|
||||||
|
<p className={styles.systemTransfer__subtitle}>
|
||||||
|
从系统账户(总部、运营、积分股池等)划转资金到用户账户
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 主内容卡片 */}
|
||||||
|
<div className={styles.systemTransfer__card}>
|
||||||
|
{/* 标签页 */}
|
||||||
|
<div className={styles.systemTransfer__tabs}>
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
styles.systemTransfer__tab,
|
||||||
|
activeTab === 'transfer' && styles['systemTransfer__tab--active']
|
||||||
|
)}
|
||||||
|
onClick={() => setActiveTab('transfer')}
|
||||||
|
>
|
||||||
|
发起划转
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
styles.systemTransfer__tab,
|
||||||
|
activeTab === 'orders' && styles['systemTransfer__tab--active']
|
||||||
|
)}
|
||||||
|
onClick={() => setActiveTab('orders')}
|
||||||
|
>
|
||||||
|
划转记录
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 内容区域 */}
|
||||||
|
{activeTab === 'transfer' ? renderTransferForm() : renderOrderList()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 查看详情弹窗 */}
|
||||||
|
<Modal
|
||||||
|
visible={!!viewingOrder}
|
||||||
|
title="划转订单详情"
|
||||||
|
onClose={() => setViewingOrder(null)}
|
||||||
|
footer={
|
||||||
|
<div className={styles.systemTransfer__modalFooter}>
|
||||||
|
<Button variant="outline" onClick={() => setViewingOrder(null)}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
{viewingOrder && (
|
||||||
|
<div className={styles.systemTransfer__detail}>
|
||||||
|
<div className={styles.systemTransfer__detailSection}>
|
||||||
|
<div className={styles.systemTransfer__detailTitle}>订单信息</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>订单号:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>{viewingOrder.orderNo}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>金额:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{parseFloat(viewingOrder.amount).toFixed(2)} USDT
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>状态:</span>
|
||||||
|
<span
|
||||||
|
className={styles.systemTransfer__statusTag}
|
||||||
|
style={{
|
||||||
|
backgroundColor: getSystemWithdrawalStatusInfo(viewingOrder.status).color,
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getSystemWithdrawalStatusInfo(viewingOrder.status).label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>创建时间:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{formatDateTime(viewingOrder.createdAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.systemTransfer__detailSection}>
|
||||||
|
<div className={styles.systemTransfer__detailTitle}>源账户</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>账户序列:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{viewingOrder.fromAccountSequence}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>账户名称:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{viewingOrder.fromAccountName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.systemTransfer__detailSection}>
|
||||||
|
<div className={styles.systemTransfer__detailTitle}>目标账户</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>账户序列:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{viewingOrder.toAccountSequence}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>用户ID:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>{viewingOrder.toUserId}</span>
|
||||||
|
</div>
|
||||||
|
{viewingOrder.toUserName && (
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>用户姓名:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{viewingOrder.toUserName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>钱包地址:</span>
|
||||||
|
<span
|
||||||
|
className={styles.systemTransfer__detailValue}
|
||||||
|
style={{ wordBreak: 'break-all' }}
|
||||||
|
>
|
||||||
|
{viewingOrder.toAddress}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(viewingOrder.txHash || viewingOrder.errorMessage) && (
|
||||||
|
<div className={styles.systemTransfer__detailSection}>
|
||||||
|
<div className={styles.systemTransfer__detailTitle}>区块链信息</div>
|
||||||
|
{viewingOrder.txHash && (
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>TxHash:</span>
|
||||||
|
<span
|
||||||
|
className={styles.systemTransfer__detailValue}
|
||||||
|
style={{ wordBreak: 'break-all', fontFamily: 'monospace' }}
|
||||||
|
>
|
||||||
|
{viewingOrder.txHash}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{viewingOrder.errorMessage && (
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>错误信息:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue} style={{ color: '#ff4d4f' }}>
|
||||||
|
{viewingOrder.errorMessage}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{viewingOrder.memo && (
|
||||||
|
<div className={styles.systemTransfer__detailSection}>
|
||||||
|
<div className={styles.systemTransfer__detailTitle}>备注</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>{viewingOrder.memo}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.systemTransfer__detailSection}>
|
||||||
|
<div className={styles.systemTransfer__detailTitle}>操作信息</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>操作员:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{viewingOrder.operatorName || viewingOrder.operatorId}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{viewingOrder.frozenAt && (
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>冻结时间:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{formatDateTime(viewingOrder.frozenAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{viewingOrder.broadcastedAt && (
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>广播时间:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{formatDateTime(viewingOrder.broadcastedAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{viewingOrder.confirmedAt && (
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>确认时间:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{formatDateTime(viewingOrder.confirmedAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* 确认划转弹窗 */}
|
||||||
|
<Modal
|
||||||
|
visible={confirmModalOpen}
|
||||||
|
title="确认划转"
|
||||||
|
onClose={() => setConfirmModalOpen(false)}
|
||||||
|
footer={
|
||||||
|
<div className={styles.systemTransfer__modalFooter}>
|
||||||
|
<Button variant="outline" onClick={() => setConfirmModalOpen(false)}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={handleSubmitTransfer}
|
||||||
|
loading={requestMutation.isPending}
|
||||||
|
>
|
||||||
|
确认划转
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
width={500}
|
||||||
|
>
|
||||||
|
<div className={styles.systemTransfer__detail}>
|
||||||
|
<div className={styles.systemTransfer__warning}>
|
||||||
|
请仔细核对划转信息,划转一旦提交将无法撤销。
|
||||||
|
</div>
|
||||||
|
<div className={styles.systemTransfer__detailSection}>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>源账户:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{selectedAccount?.name} ({selectedAccount?.accountSequence})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>目标账户:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{transferForm.toAccountSequence}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>划转金额:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>
|
||||||
|
{transferForm.amount} USDT
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{transferForm.memo && (
|
||||||
|
<div className={styles.systemTransfer__detailRow}>
|
||||||
|
<span className={styles.systemTransfer__detailLabel}>备注:</span>
|
||||||
|
<span className={styles.systemTransfer__detailValue}>{transferForm.memo}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,427 @@
|
||||||
|
@use '@/styles/variables' as *;
|
||||||
|
|
||||||
|
.systemTransfer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__card {
|
||||||
|
background: $card-background;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: $shadow-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签页
|
||||||
|
&__tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tab {
|
||||||
|
padding: 12px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $text-secondary;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: $primary-color;
|
||||||
|
border-bottom-color: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选区域
|
||||||
|
&__filters {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: white;
|
||||||
|
min-width: 140px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__select {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: white;
|
||||||
|
min-width: 140px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 列表区域
|
||||||
|
&__list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loading,
|
||||||
|
&__empty,
|
||||||
|
&__error {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 60px 20px;
|
||||||
|
color: $text-secondary;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
color: $error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格样式
|
||||||
|
&__table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-secondary;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
color: $text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
transition: background 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__orderNo {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__amount {
|
||||||
|
font-weight: 600;
|
||||||
|
color: $primary-color;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accountTag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: #f0f5ff;
|
||||||
|
color: #2f54eb;
|
||||||
|
|
||||||
|
&--system {
|
||||||
|
background: #fff7e6;
|
||||||
|
color: #fa8c16;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--user {
|
||||||
|
background: #e6f7ff;
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__statusTag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accountSequence {
|
||||||
|
font-weight: 600;
|
||||||
|
color: $primary-color;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accountName {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__txHash {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 11px;
|
||||||
|
color: $text-secondary;
|
||||||
|
max-width: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 操作按钮
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actionBtn {
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: $text-secondary;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary-color;
|
||||||
|
border-color: $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
color: $primary-color;
|
||||||
|
border-color: $primary-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e6f4ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
&__pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid $border-color;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__pageButtons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建划转表单
|
||||||
|
&__form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__formGroup {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: $text-disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__formHint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $text-secondary;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__formError {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $error-color;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__modalFooter {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详情样式
|
||||||
|
&__detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__detailSection {
|
||||||
|
padding: 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__detailTitle {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-primary;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__detailRow {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__detailLabel {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 80px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: $text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__detailValue {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
color: $text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 账户卡片列表
|
||||||
|
&__accountCards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accountCard {
|
||||||
|
padding: 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $primary-color;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--selected {
|
||||||
|
border-color: $primary-color;
|
||||||
|
background: #e6f4ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accountCardName {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-primary;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accountCardSequence {
|
||||||
|
font-size: 13px;
|
||||||
|
color: $text-secondary;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accountCardBalance {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $primary-color;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 警告信息
|
||||||
|
&__warning {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fffbe6;
|
||||||
|
border: 1px solid #ffe58f;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #ad6800;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ const topMenuItems: MenuItem[] = [
|
||||||
{ key: 'notifications', icon: '/images/Container3.svg', label: '通知管理', path: '/notifications' },
|
{ key: 'notifications', icon: '/images/Container3.svg', label: '通知管理', path: '/notifications' },
|
||||||
{ key: 'pending-actions', icon: '/images/Container3.svg', label: '待办操作', path: '/pending-actions' },
|
{ key: 'pending-actions', icon: '/images/Container3.svg', label: '待办操作', path: '/pending-actions' },
|
||||||
{ key: 'withdrawals', icon: '/images/Container5.svg', label: '提现审核', path: '/withdrawals' },
|
{ key: 'withdrawals', icon: '/images/Container5.svg', label: '提现审核', path: '/withdrawals' },
|
||||||
|
{ key: 'system-transfer', icon: '/images/Container5.svg', label: '系统划转', path: '/system-transfer' },
|
||||||
{ key: 'statistics', icon: '/images/Container5.svg', label: '数据统计', path: '/statistics' },
|
{ key: 'statistics', icon: '/images/Container5.svg', label: '数据统计', path: '/statistics' },
|
||||||
{ key: 'maintenance', icon: '/images/Container6.svg', label: '系统维护', path: '/maintenance' },
|
{ key: 'maintenance', icon: '/images/Container6.svg', label: '系统维护', path: '/maintenance' },
|
||||||
{ key: 'settings', icon: '/images/Container6.svg', label: '系统设置', path: '/settings' },
|
{ key: 'settings', icon: '/images/Container6.svg', label: '系统设置', path: '/settings' },
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@
|
||||||
export * from './useDashboard';
|
export * from './useDashboard';
|
||||||
export * from './useUsers';
|
export * from './useUsers';
|
||||||
export * from './useAuthorizations';
|
export * from './useAuthorizations';
|
||||||
|
export * from './useSystemWithdrawal';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* 系统账户划转管理 Hooks
|
||||||
|
* [2026-01-06] 新增
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { systemWithdrawalService } from '@/services/systemWithdrawalService';
|
||||||
|
import type {
|
||||||
|
SystemWithdrawalQueryParams,
|
||||||
|
SystemWithdrawalRequest,
|
||||||
|
} from '@/types/system-withdrawal.types';
|
||||||
|
|
||||||
|
/** Query Keys */
|
||||||
|
export const systemWithdrawalKeys = {
|
||||||
|
all: ['systemWithdrawals'] as const,
|
||||||
|
accounts: () => [...systemWithdrawalKeys.all, 'accounts'] as const,
|
||||||
|
orders: (params: SystemWithdrawalQueryParams) => [...systemWithdrawalKeys.all, 'orders', params] as const,
|
||||||
|
accountName: (accountSequence: string) => [...systemWithdrawalKeys.all, 'accountName', accountSequence] as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可划转的系统账户列表
|
||||||
|
*/
|
||||||
|
export function useSystemWithdrawalAccounts() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: systemWithdrawalKeys.accounts(),
|
||||||
|
queryFn: () => systemWithdrawalService.getAccounts(),
|
||||||
|
staleTime: 5 * 60 * 1000, // 5分钟
|
||||||
|
gcTime: 10 * 60 * 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取划转订单列表
|
||||||
|
*/
|
||||||
|
export function useSystemWithdrawalOrders(params: SystemWithdrawalQueryParams = {}) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: systemWithdrawalKeys.orders(params),
|
||||||
|
queryFn: () => systemWithdrawalService.getOrders(params),
|
||||||
|
staleTime: 30 * 1000, // 30秒
|
||||||
|
gcTime: 5 * 60 * 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取账户名称
|
||||||
|
*/
|
||||||
|
export function useAccountName(accountSequence: string) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: systemWithdrawalKeys.accountName(accountSequence),
|
||||||
|
queryFn: () => systemWithdrawalService.getAccountName(accountSequence),
|
||||||
|
enabled: !!accountSequence && accountSequence.length > 0,
|
||||||
|
staleTime: 10 * 60 * 1000, // 10分钟
|
||||||
|
gcTime: 30 * 60 * 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起划转请求
|
||||||
|
*/
|
||||||
|
export function useRequestSystemWithdrawal() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (data: SystemWithdrawalRequest) => systemWithdrawalService.request(data),
|
||||||
|
onSuccess: () => {
|
||||||
|
// 成功后刷新账户列表和订单列表
|
||||||
|
queryClient.invalidateQueries({ queryKey: systemWithdrawalKeys.all });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -207,6 +207,18 @@ export const API_ENDPOINTS = {
|
||||||
COMPLETE_PAYMENT: (orderNo: string) => `/v1/wallets/fiat-withdrawals/${orderNo}/complete-payment`,
|
COMPLETE_PAYMENT: (orderNo: string) => `/v1/wallets/fiat-withdrawals/${orderNo}/complete-payment`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// [2026-01-06] 新增:系统账户划转 (wallet-service)
|
||||||
|
SYSTEM_WITHDRAWAL: {
|
||||||
|
// 发起划转请求
|
||||||
|
REQUEST: '/v1/wallets/system-withdrawal/request',
|
||||||
|
// 获取可划转账户列表
|
||||||
|
ACCOUNTS: '/v1/wallets/system-withdrawal/accounts',
|
||||||
|
// 查询划转订单
|
||||||
|
ORDERS: '/v1/wallets/system-withdrawal/orders',
|
||||||
|
// 获取账户名称
|
||||||
|
ACCOUNT_NAME: (accountSequence: string) => `/v1/wallets/system-withdrawal/account-name/${accountSequence}`,
|
||||||
|
},
|
||||||
|
|
||||||
// [2026-01-04] 新增:系统账户报表 (reporting-service)
|
// [2026-01-04] 新增:系统账户报表 (reporting-service)
|
||||||
// 回滚方式:删除此部分即可
|
// 回滚方式:删除此部分即可
|
||||||
SYSTEM_ACCOUNT_REPORTS: {
|
SYSTEM_ACCOUNT_REPORTS: {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* 系统账户划转服务
|
||||||
|
* [2026-01-06] 新增
|
||||||
|
*/
|
||||||
|
|
||||||
|
import apiClient from '@/infrastructure/api/client';
|
||||||
|
import { API_ENDPOINTS } from '@/infrastructure/api/endpoints';
|
||||||
|
import type {
|
||||||
|
SystemAccount,
|
||||||
|
SystemWithdrawalRequest,
|
||||||
|
SystemWithdrawalResponse,
|
||||||
|
SystemWithdrawalQueryParams,
|
||||||
|
SystemWithdrawalOrderListResponse,
|
||||||
|
} from '@/types/system-withdrawal.types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统账户划转服务
|
||||||
|
*/
|
||||||
|
export const systemWithdrawalService = {
|
||||||
|
/**
|
||||||
|
* 获取可划转的系统账户列表
|
||||||
|
*/
|
||||||
|
async getAccounts(): Promise<SystemAccount[]> {
|
||||||
|
const response = await apiClient.get(API_ENDPOINTS.SYSTEM_WITHDRAWAL.ACCOUNTS);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const result = (response as any)?.data?.data;
|
||||||
|
return result ?? [];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取划转订单列表
|
||||||
|
*/
|
||||||
|
async getOrders(params: SystemWithdrawalQueryParams = {}): Promise<SystemWithdrawalOrderListResponse> {
|
||||||
|
const response = await apiClient.get(API_ENDPOINTS.SYSTEM_WITHDRAWAL.ORDERS, { params });
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const result = (response as any)?.data?.data;
|
||||||
|
return result ?? { items: [], total: 0, page: 1, limit: 20 };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取账户名称
|
||||||
|
*/
|
||||||
|
async getAccountName(accountSequence: string): Promise<{ accountSequence: string; name: string }> {
|
||||||
|
const response = await apiClient.get(API_ENDPOINTS.SYSTEM_WITHDRAWAL.ACCOUNT_NAME(accountSequence));
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const result = (response as any)?.data?.data;
|
||||||
|
return result ?? { accountSequence, name: '未知账户' };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起划转请求
|
||||||
|
*/
|
||||||
|
async request(data: SystemWithdrawalRequest): Promise<SystemWithdrawalResponse> {
|
||||||
|
const response = await apiClient.post(API_ENDPOINTS.SYSTEM_WITHDRAWAL.REQUEST, data);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return (response as any)?.data?.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default systemWithdrawalService;
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* 系统账户划转类型定义
|
||||||
|
* [2026-01-06] 新增
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 划转订单状态
|
||||||
|
export type SystemWithdrawalStatus = 'PENDING' | 'FROZEN' | 'BROADCASTED' | 'CONFIRMED' | 'FAILED';
|
||||||
|
|
||||||
|
// 系统账户信息
|
||||||
|
export interface SystemAccount {
|
||||||
|
accountSequence: string;
|
||||||
|
name: string;
|
||||||
|
balance?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 划转订单
|
||||||
|
export interface SystemWithdrawalOrder {
|
||||||
|
orderId: number;
|
||||||
|
orderNo: string;
|
||||||
|
fromAccountSequence: string;
|
||||||
|
fromAccountName: string;
|
||||||
|
toAccountSequence: string;
|
||||||
|
toUserId: number;
|
||||||
|
toUserName?: string;
|
||||||
|
toAddress: string;
|
||||||
|
amount: string;
|
||||||
|
chainType: string;
|
||||||
|
txHash?: string;
|
||||||
|
status: SystemWithdrawalStatus;
|
||||||
|
errorMessage?: string;
|
||||||
|
operatorId: string;
|
||||||
|
operatorName?: string;
|
||||||
|
memo?: string;
|
||||||
|
frozenAt?: string;
|
||||||
|
broadcastedAt?: string;
|
||||||
|
confirmedAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 划转请求
|
||||||
|
export interface SystemWithdrawalRequest {
|
||||||
|
fromAccountSequence: string;
|
||||||
|
toAccountSequence: string;
|
||||||
|
amount: string;
|
||||||
|
memo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 划转响应
|
||||||
|
export interface SystemWithdrawalResponse {
|
||||||
|
orderNo: string;
|
||||||
|
status: SystemWithdrawalStatus;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单查询参数
|
||||||
|
export interface SystemWithdrawalQueryParams {
|
||||||
|
fromAccountSequence?: string;
|
||||||
|
toAccountSequence?: string;
|
||||||
|
status?: SystemWithdrawalStatus;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单列表响应
|
||||||
|
export interface SystemWithdrawalOrderListResponse {
|
||||||
|
items: SystemWithdrawalOrder[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态配置
|
||||||
|
export const SYSTEM_WITHDRAWAL_STATUS_CONFIG: Record<SystemWithdrawalStatus, { label: string; color: string }> = {
|
||||||
|
PENDING: { label: '待处理', color: '#faad14' },
|
||||||
|
FROZEN: { label: '已冻结', color: '#1890ff' },
|
||||||
|
BROADCASTED: { label: '已广播', color: '#722ed1' },
|
||||||
|
CONFIRMED: { label: '已确认', color: '#52c41a' },
|
||||||
|
FAILED: { label: '失败', color: '#ff4d4f' },
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态信息
|
||||||
|
export function getSystemWithdrawalStatusInfo(status: SystemWithdrawalStatus) {
|
||||||
|
return SYSTEM_WITHDRAWAL_STATUS_CONFIG[status] || { label: status, color: '#999' };
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue