import Add from "@mui/icons-material/Add"; import ArrowDownwardOutlined from "@mui/icons-material/ArrowDownwardOutlined"; import ArrowUpwardOutlined from "@mui/icons-material/ArrowUpwardOutlined"; import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined"; import ReceiptLongOutlined from "@mui/icons-material/ReceiptLongOutlined"; import SettingsOutlined from "@mui/icons-material/SettingsOutlined"; import SwapVertOutlined from "@mui/icons-material/SwapVertOutlined"; import Button from "@mui/material/Button"; import MenuItem from "@mui/material/MenuItem"; import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx"; import TextField from "@mui/material/TextField"; import { AdminFormAmountField, AdminFormFieldGrid, AdminFormReadOnlyField, AdminFormSection, } from "@/shared/ui/AdminFormDialog.jsx"; import { formatMillis } from "@/shared/utils/time.js"; import { DataState } from "@/shared/ui/DataState.jsx"; import { AdminActionIconButton, AdminRowActions } from "@/shared/ui/AdminListLayout.jsx"; import { InlineEditableCell } from "@/shared/ui/InlineEditableCell.jsx"; import { SideDrawer } from "@/shared/ui/SideDrawer.jsx"; import { coinSellerStatusFilters } from "@/features/host-org/constants.js"; import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx"; import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx"; import { HostOrgToolbar } from "@/features/host-org/components/HostOrgToolbar.jsx"; import { useHostCoinSellersPage } from "@/features/host-org/hooks/useHostCoinSellersPage.js"; import { CoinSellerLedgerTable } from "@/features/operations/components/CoinSellerLedgerTable.jsx"; import { createRegionColumnFilter } from "@/shared/ui/RegionFilterControl.jsx"; import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js"; import styles from "@/features/host-org/host-org.module.css"; const baseColumns = [ { key: "seller", label: "币商", render: (item) => , width: "minmax(220px, 1.3fr)", }, { key: "regionId", label: "区域", render: (item, _index, context) => regionName(item, context?.regionOptions), width: "minmax(140px, 0.8fr)", }, { key: "contact", label: "联系方式", render: (item, _index, context) => , width: "minmax(170px, 1fr)", }, { key: "merchantBalance", label: "币商余额", sortable: true, sortField: "merchant_balance", render: (item) => formatNumber(item.merchantBalance), width: "minmax(120px, 0.8fr)", }, { key: "totalRechargeUsdt", label: "累充USDT", sortable: true, sortField: "total_recharge_usdt", render: (item) => formatUSDTMicro(item.totalRechargeUsdtMicro), width: "minmax(130px, 0.8fr)", }, { key: "updatedAtMs", label: "更新时间", render: (item) => formatMillis(item.updatedAtMs), width: "minmax(170px, 1fr)", }, ]; export function HostCoinSellersPage() { const page = useHostCoinSellersPage(); const items = page.data.items || []; const total = page.data.total || 0; const createDisabled = !page.abilities.canCreate; const updateDisabled = !page.abilities.canUpdate; const columns = [ ...baseColumns.slice(0, 2), { key: "status", label: "状态", render: (item) => , width: "minmax(90px, 0.6fr)", }, ...baseColumns.slice(2), page.abilities.canStockCredit || page.abilities.canLedger ? { key: "actions", label: "操作", render: (item) => , width: "minmax(90px, 0.5fr)", } : null, ] .filter(Boolean) .map((column) => { if (column.key === "seller") { return { ...column, filter: createTextColumnFilter({ placeholder: "搜索用户 ID、展示 ID、用户名", value: page.query, onChange: page.changeQuery, }), }; } if (column.key === "status") { return { ...column, filter: createOptionsColumnFilter({ options: coinSellerStatusFilters, placeholder: "搜索状态", value: page.status, onChange: page.changeStatus, }), }; } if (column.sortable) { return { ...column, header: ( ), }; } return column; }); const regionFilter = createRegionColumnFilter({ loading: page.loadingRegions, options: page.regionOptions, value: page.regionId, onChange: page.changeRegionId, }); const leadingActions = [ page.abilities.canExchangeRate ? { icon: , label: "工资兑换比例", onClick: page.openRateSettings } : null, ].filter(Boolean); const toolbarActions = [ page.abilities.canCreate ? { icon: , label: "添加币商", onClick: page.openCreateSeller, variant: "primary" } : null, ].filter(Boolean); return (
0 ? { page: page.page, pageSize: page.data.pageSize || 50, total, onPageChange: page.setPage, } : undefined } regionFilter={regionFilter} rowKey={(item) => item.userId} />
page.setCreateForm({ ...page.createForm, targetUserId: event.target.value })} /> page.setCreateForm({ ...page.createForm, contact: event.target.value })} /> page.setEditForm({ ...page.editForm, contact: event.target.value })} /> page.setStockForm({ ...page.stockForm, type: event.target.value, rechargeAmount: "", }) } > USDT进货 金币补偿 page.setStockForm({ ...page.stockForm, coinAmount: event.target.value }) } /> {page.stockForm.type === "usdt_purchase" ? ( page.setStockForm({ ...page.stockForm, rechargeAmount: event.target.value }) } /> ) : null} page.setStockForm({ ...page.stockForm, reason: event.target.value })} /> {page.selectedSeller ? ( ) : null} } open={page.activeAction === "rates"} title="币商工资兑换比例" width="wide" onClose={page.closeAction} >
page.changeRateRegionId(event.target.value)} > {page.regionOptions.map((region) => ( {region.label} ))}
区间配置
{page.rateTiers.map((tier, index) => (
page.updateRateTier(index, { minUsd: event.target.value })} /> page.updateRateTier(index, { maxUsd: event.target.value })} /> page.updateRateTier(index, { coinPerUsd: event.target.value }) } />
page.updateRateTier(index, { enabled: event.target.checked })} />
page.removeRateTier(index)} >
))}
); } function SellerIdentity({ item }) { const name = item.username || "-"; const meta = item.displayUserId || item.userId; return (
{item.avatar ? ( ) : ( {name.slice(0, 1).toUpperCase()} )} {name} {meta}
); } function SellerStatusSwitch({ item, page }) { return ( page.toggleSeller(item, event.target.checked)} /> ); } function SellerContact({ item, page }) { const contact = String(item.contact || "").trim(); return ( String(value ?? "").trim()} saving={Boolean(page?.contactSavingIds?.[item.userId])} value={contact} variant="link" width="100%" onSave={(nextValue) => page.saveSellerContact(item, nextValue)} /> ); } function SellerActions({ item, page }) { return ( {page.abilities.canLedger ? ( page.openSellerLedger(item)}> ) : null} {page.abilities.canStockCredit ? ( page.openStockCredit(item)} > ) : null} ); } function sellerDrawerTitle(seller) { if (!seller) { return ""; } const name = seller.username || "-"; const displayId = seller.displayUserId || seller.userId || ""; return displayId ? `${name} / ${displayId}` : name; } function regionName(item, regionOptions = []) { if (item.regionName) { return item.regionName; } const option = regionOptions.find((region) => String(region.value) === String(item.regionId)); return option?.name || item.regionId || "-"; } function formatNumber(value) { return Number(value || 0).toLocaleString("zh-CN"); } function formatUSDTMicro(value) { const amount = Number(value || 0); if (!Number.isFinite(amount) || amount <= 0) { return "0"; } const whole = Math.trunc(amount / 1_000_000); const fraction = Math.trunc(Math.abs(amount % 1_000_000)); const trimmedFraction = String(fraction).padStart(6, "0").replace(/0+$/, ""); return trimmedFraction ? `${whole}.${trimmedFraction}` : String(whole); } function SortHeader({ field, label, onToggle, sortBy, sortDirection }) { const active = sortBy === field; const SortIcon = active && sortDirection === "asc" ? ArrowUpwardOutlined : active && sortDirection === "desc" ? ArrowDownwardOutlined : SwapVertOutlined; const directionLabel = sortDirection === "asc" ? "升序" : "降序"; const nextDirectionLabel = active && sortDirection === "desc" ? "升序" : "降序"; return ( ); } const coinLedgerActionSx = { borderColor: "rgba(37, 99, 235, 0.28)", backgroundColor: "rgba(37, 99, 235, 0.08)", color: "#1d4ed8", "&:hover": { borderColor: "rgba(37, 99, 235, 0.48)", backgroundColor: "rgba(37, 99, 235, 0.14)", color: "#1e40af", }, "& .MuiSvgIcon-root": { color: "#2563eb", }, }; const coinCreditActionSx = { borderColor: "rgba(245, 158, 11, 0.45)", backgroundColor: "rgba(251, 191, 36, 0.16)", color: "#b45309", "&:hover": { borderColor: "rgba(245, 158, 11, 0.72)", backgroundColor: "rgba(251, 191, 36, 0.24)", color: "#92400e", }, "& .MuiSvgIcon-root": { color: "#f59e0b", }, };