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 EditOutlined from "@mui/icons-material/EditOutlined"; import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined"; import RemoveCircleOutlineOutlined from "@mui/icons-material/RemoveCircleOutlineOutlined"; 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, AdminFormSwitchField, } 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 { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.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, createUserIdentityColumnFilter } 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)", }, ]; const subSellerColumns = [ { 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: "status", label: "状态", render: (item) => item.status || "-", width: "minmax(90px, 0.5fr)", }, { key: "merchantBalance", label: "币商余额", render: (item) => formatNumber(item.merchantBalance), width: "minmax(120px, 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 stockDebitForm = page.stockDebitForm || { coinAmount: "", reason: "" }; 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)", }, { key: "canManageSubCoinSellers", label: "子币商权限", render: (item) => , width: "minmax(120px, 0.7fr)", }, { key: "subCoinSellerCount", label: "子币商数量", render: (item) => , width: "minmax(120px, 0.7fr)", }, ...baseColumns.slice(2), page.abilities.canUpdate || page.abilities.canStockCredit || page.abilities.canLedger ? { key: "actions", label: "操作", render: (item) => , width: "minmax(160px, 0.75fr)", } : null, ] .filter(Boolean) .map((column) => { if (column.key === "seller") { return { ...column, filter: createUserIdentityColumnFilter({ value: page.userFilter, onChange: page.changeUserFilter, }), }; } 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.setCreateForm({ ...page.createForm, canManageSubCoinSellers: checked })} /> page.setEditForm({ ...page.editForm, contact: event.target.value })} /> page.setEditForm({ ...page.editForm, canManageSubCoinSellers: checked })} /> 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.setStockDebitForm?.({ ...stockDebitForm, coinAmount: event.target.value }) } /> page.setStockDebitForm?.({ ...stockDebitForm, rechargeAmount: event.target.value }) } /> page.setStockDebitForm?.({ ...stockDebitForm, reason: event.target.value }) } /> {page.selectedSeller ? ( ) : null} 0 ? { page: page.subSellerPage, pageSize: page.subSellerData.pageSize || 50, total: page.subSellerData.total, onPageChange: page.setSubSellerPage, } : undefined } rowKey={(item) => item.userId} /> } 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 }) { return ; } function SellerStatusSwitch({ item, page }) { return ( page.toggleSeller(item, event.target.checked)} /> ); } function SubCoinSellerPermissionSwitch({ item, page }) { return ( page.toggleSubCoinSellerPermission(item, event.target.checked)} /> ); } function SubCoinSellerCountButton({ item, page }) { const count = Number(item.subCoinSellerCount || 0); return ( ); } 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.canUpdate ? ( page.openEditSeller(item)} > ) : null} {page.abilities.canLedger ? ( page.openSellerLedger(item)} > ) : null} {page.abilities.canStockCredit ? ( page.openStockCredit(item)} > ) : null} {page.abilities.canStockCredit ? ( page.openStockDebit(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", }, }; const coinDebitActionSx = { borderColor: "rgba(220, 38, 38, 0.34)", backgroundColor: "rgba(254, 226, 226, 0.65)", color: "#b91c1c", "&:hover": { borderColor: "rgba(220, 38, 38, 0.58)", backgroundColor: "rgba(254, 202, 202, 0.75)", color: "#991b1b", }, "& .MuiSvgIcon-root": { color: "#dc2626", }, };