diff --git a/frontend/admin-web/src/app/(dashboard)/pre-planting/page.tsx b/frontend/admin-web/src/app/(dashboard)/pre-planting/page.tsx new file mode 100644 index 00000000..d1b6d1f4 --- /dev/null +++ b/frontend/admin-web/src/app/(dashboard)/pre-planting/page.tsx @@ -0,0 +1,475 @@ +'use client'; + +/** + * [2026-02-17] 预种计划管理页面 + * + * 管理员端的预种计划管理页面,提供: + * - 预种开关控制(开启/关闭预种购买功能) + * - 统计汇总(总订单、总份数、总金额、合成树数、参与用户数) + * - 预种订单列表(按用户/状态过滤) + * - 预种持仓列表(查看用户持仓概况) + * - 合并记录列表(查看合并和签约状态) + * + * === 技术栈 === + * - React Query (usePrePlanting* hooks) 数据获取 + * - SCSS Modules 样式 + * - PageContainer 布局包裹 + * + * === 与现有页面的关系 === + * 完全独立页面,不修改任何现有管理页面。 + * 侧边栏入口在"数据统计"和"系统维护"之间。 + */ + +import { useState } from 'react'; +import { Button } from '@/components/common'; +import { PageContainer } from '@/components/layout'; +import { cn } from '@/utils/helpers'; +import { formatDateTime } from '@/utils/formatters'; +import { + usePrePlantingConfig, + usePrePlantingStats, + usePrePlantingOrders, + usePrePlantingPositions, + usePrePlantingMerges, + useTogglePrePlantingConfig, +} from '@/hooks'; +import type { + PrePlantingAdminOrder, + PrePlantingAdminPosition, + PrePlantingAdminMerge, +} from '@/services/prePlantingService'; +import styles from './pre-planting.module.scss'; + +// Tab 类型定义 +type TabKey = 'orders' | 'positions' | 'merges'; + +// 订单状态显示映射 +const ORDER_STATUS_MAP: Record = { + CREATED: { label: '待支付', style: 'created' }, + PAID: { label: '已支付', style: 'paid' }, + MERGED: { label: '已合并', style: 'merged' }, +}; + +// 合同状态显示映射 +const CONTRACT_STATUS_MAP: Record = { + PENDING: { label: '待签约', style: 'pending' }, + SIGNED: { label: '已签约', style: 'signed' }, + EXPIRED: { label: '已过期', style: 'expired' }, +}; + +/** + * 预种计划管理页面 + */ +export default function PrePlantingPage() { + // === Tab 与分页状态 === + const [activeTab, setActiveTab] = useState('orders'); + const [keyword, setKeyword] = useState(''); + const [page, setPage] = useState(1); + const pageSize = 20; + + // === React Query Hooks === + const { data: config, isLoading: configLoading } = usePrePlantingConfig(); + const { data: stats, isLoading: statsLoading } = usePrePlantingStats(); + const toggleConfig = useTogglePrePlantingConfig(); + + const ordersQuery = usePrePlantingOrders({ + page, + pageSize, + keyword: activeTab === 'orders' ? keyword : undefined, + }); + const positionsQuery = usePrePlantingPositions({ + page, + pageSize, + keyword: activeTab === 'positions' ? keyword : undefined, + }); + const mergesQuery = usePrePlantingMerges({ + page, + pageSize, + keyword: activeTab === 'merges' ? keyword : undefined, + }); + + // === 开关切换 === + const handleToggle = () => { + if (!config || toggleConfig.isPending) return; + toggleConfig.mutate(!config.isActive); + }; + + // === Tab 切换时重置分页 === + const handleTabChange = (tab: TabKey) => { + setActiveTab(tab); + setPage(1); + setKeyword(''); + }; + + // === 格式化数字(千分位) === + const formatNumber = (n: number) => + n.toLocaleString('en-US'); + + return ( + +
+ {/* 页面标题 */} +
+

预种计划管理

+
+ + {/* 预种开关卡片 */} +
+
+ + {config?.isActive ? '🌱' : '⏸️'} + +
+
+ 预种功能状态 + + {configLoading ? '加载中' : config?.isActive ? '已开启' : '已关闭'} + +
+
+ {config?.isActive + ? '用户可正常购买预种份额(3171 USDT/份)' + : '新用户不可购买;已有未凑满份额的用户可继续购买至 5 的倍数'} +
+
+
+ +
+ + {/* 统计卡片 */} +
+
+
+ {statsLoading ? '-' : formatNumber(stats?.totalOrders ?? 0)} +
+
总订单数
+
+
+
+ {statsLoading ? '-' : formatNumber(stats?.totalPortions ?? 0)} +
+
总份数
+
+
+
+ {statsLoading ? '-' : formatNumber(stats?.totalAmount ?? 0)} +
+
总金额 (USDT)
+
+
+
+ {statsLoading ? '-' : formatNumber(stats?.totalTreesMerged ?? 0)} +
+
已合成树数
+
+
+ + {/* Tab 切换 */} +
+ + + +
+ + {/* 数据内容区 */} +
+ {/* 搜索栏 */} +
+
+ { + setKeyword(e.target.value); + setPage(1); + }} + /> +
+ +
+ + {/* Tab 对应的表格 */} + {activeTab === 'orders' && ( + ordersQuery.refetch()} + /> + )} + {activeTab === 'positions' && ( + positionsQuery.refetch()} + /> + )} + {activeTab === 'merges' && ( + mergesQuery.refetch()} + /> + )} +
+
+
+ ); +} + +// ============================================ +// 子组件:订单表格 +// ============================================ + +function OrdersTable({ + data, + loading, + error, + onRetry, +}: { + data: PrePlantingAdminOrder[]; + loading: boolean; + error: Error | null; + onRetry: () => void; +}) { + if (loading) return
加载中...
; + if (error) { + return ( +
+ {error.message || '加载失败'} + +
+ ); + } + if (data.length === 0) return
暂无预种订单
; + + return ( + + + + + + + + + + + + + + {data.map((order) => { + const status = ORDER_STATUS_MAP[order.status] ?? { label: order.status, style: '' }; + return ( + + + + + + + + + + ); + })} + +
订单号用户账号份数金额 (USDT)状态支付时间创建时间
{order.orderNo}{order.accountSequence}{order.portionCount}{order.totalAmount.toLocaleString()} + + {status.label} + + {order.paidAt ? formatDateTime(order.paidAt) : '-'}{formatDateTime(order.createdAt)}
+ ); +} + +// ============================================ +// 子组件:持仓表格 +// ============================================ + +function PositionsTable({ + data, + loading, + error, + onRetry, +}: { + data: PrePlantingAdminPosition[]; + loading: boolean; + error: Error | null; + onRetry: () => void; +}) { + if (loading) return
加载中...
; + if (error) { + return ( +
+ {error.message || '加载失败'} + +
+ ); + } + if (data.length === 0) return
暂无持仓记录
; + + return ( + + + + + + + + + + + + + + {data.map((pos) => ( + + + + + + + + + + ))} + +
用户账号累计份数待合并已合并合成树数省市首次购买
{pos.accountSequence}{pos.totalPortions}{pos.availablePortions} / 5{pos.mergedPortions}{pos.totalTreesMerged}{pos.provinceCode && pos.cityCode ? `${pos.provinceCode} · ${pos.cityCode}` : '-'}{pos.firstPurchaseAt ? formatDateTime(pos.firstPurchaseAt) : '-'}
+ ); +} + +// ============================================ +// 子组件:合并记录表格 +// ============================================ + +function MergesTable({ + data, + loading, + error, + onRetry, +}: { + data: PrePlantingAdminMerge[]; + loading: boolean; + error: Error | null; + onRetry: () => void; +}) { + if (loading) return
加载中...
; + if (error) { + return ( +
+ {error.message || '加载失败'} + +
+ ); + } + if (data.length === 0) return
暂无合并记录
; + + return ( + + + + + + + + + + + + + + + {data.map((merge) => { + const cStatus = CONTRACT_STATUS_MAP[merge.contractStatus] ?? { + label: merge.contractStatus, + style: '', + }; + return ( + + + + + + + + + + + ); + })} + +
合并号用户账号树数来源订单合同状态签约时间挖矿开启合并时间
{merge.mergeNo}{merge.accountSequence}{merge.treeCount}{merge.sourceOrderNos.length} 笔 + + {cStatus.label} + + {merge.contractSignedAt ? formatDateTime(merge.contractSignedAt) : '-'}{merge.miningEnabledAt ? formatDateTime(merge.miningEnabledAt) : '-'}{formatDateTime(merge.mergedAt)}
+ ); +} diff --git a/frontend/admin-web/src/app/(dashboard)/pre-planting/pre-planting.module.scss b/frontend/admin-web/src/app/(dashboard)/pre-planting/pre-planting.module.scss new file mode 100644 index 00000000..be1bc873 --- /dev/null +++ b/frontend/admin-web/src/app/(dashboard)/pre-planting/pre-planting.module.scss @@ -0,0 +1,310 @@ +/** + * [2026-02-17] 预种计划管理页面样式 + * + * 包含:开关状态卡片、统计卡片、Tab 切换、数据表格 + * 风格与全局管理后台一致 + */ + +@use '@/styles/variables' as *; + +.prePlanting { + display: flex; + flex-direction: column; + gap: 24px; + + // 页面标题 + &__header { + display: flex; + justify-content: space-between; + align-items: center; + } + + &__title { + font-size: 24px; + font-weight: 600; + color: $text-primary; + } + + // 开关状态卡片 + &__switchCard { + background: linear-gradient(135deg, #fff9e6 0%, #fff3cc 100%); + border: 1px solid #ffd666; + border-radius: 12px; + padding: 20px 24px; + display: flex; + justify-content: space-between; + align-items: center; + + &--active { + background: linear-gradient(135deg, #f6ffed 0%, #d9f7be 100%); + border-color: #95de64; + } + } + + &__switchInfo { + display: flex; + align-items: center; + gap: 12px; + } + + &__switchIcon { + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + background: rgba(255, 255, 255, 0.8); + } + + &__switchTitle { + font-size: 16px; + font-weight: 600; + color: $text-primary; + } + + &__switchDesc { + font-size: 13px; + color: $text-secondary; + margin-top: 2px; + } + + &__switchBadge { + display: inline-block; + padding: 2px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + margin-left: 8px; + + &--on { + background: #52c41a; + color: white; + } + + &--off { + background: #faad14; + color: white; + } + } + + &__switchBtn { + padding: 8px 20px; + border-radius: 8px; + border: none; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + + &--on { + background: #ff4d4f; + color: white; + + &:hover { + background: #ff7875; + } + } + + &--off { + background: #52c41a; + color: white; + + &:hover { + background: #73d13d; + } + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + + // 统计卡片网格 + &__statsGrid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16px; + + @media (max-width: 1200px) { + grid-template-columns: repeat(2, 1fr); + } + } + + &__statCard { + background: $card-background; + border-radius: 12px; + padding: 20px; + box-shadow: $shadow-base; + text-align: center; + } + + &__statValue { + font-size: 28px; + font-weight: 700; + color: $text-primary; + } + + &__statLabel { + font-size: 13px; + color: $text-secondary; + margin-top: 4px; + } + + // Tab 切换 + &__tabs { + display: flex; + gap: 0; + border-bottom: 1px solid $border-color; + } + + &__tab { + padding: 12px 24px; + font-size: 15px; + font-weight: 500; + color: $text-secondary; + cursor: pointer; + border: none; + background: none; + border-bottom: 2px solid transparent; + transition: all 0.2s; + + &:hover { + color: $text-primary; + } + + &--active { + color: #d4af37; + border-bottom-color: #d4af37; + font-weight: 600; + } + } + + // 数据卡片容器 + &__card { + background: $card-background; + border-radius: 12px; + padding: 24px; + box-shadow: $shadow-base; + } + + &__toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + } + + &__search { + display: flex; + gap: 8px; + align-items: center; + + input { + width: 280px; + padding: 8px 12px; + border: 1px solid $border-color; + border-radius: 8px; + font-size: 14px; + outline: none; + + &:focus { + border-color: #d4af37; + } + } + } + + // 表格 + &__table { + width: 100%; + border-collapse: collapse; + + th, + td { + padding: 12px 16px; + text-align: left; + border-bottom: 1px solid $border-color; + font-size: 14px; + } + + th { + font-weight: 600; + color: $text-secondary; + background: #fafafa; + } + + td { + color: $text-primary; + } + + tr:hover td { + background: #fafafa; + } + } + + // 状态标签 + &__status { + display: inline-block; + padding: 2px 8px; + border-radius: 10px; + font-size: 12px; + font-weight: 500; + + &--created { + background: #fff7e6; + color: #d48806; + } + + &--paid { + background: #f6ffed; + color: #389e0d; + } + + &--merged { + background: #fff9e6; + color: #d4af37; + } + + &--pending { + background: #fff7e6; + color: #d48806; + } + + &--signed { + background: #f6ffed; + color: #389e0d; + } + + &--expired { + background: #f5f5f5; + color: #8c8c8c; + } + } + + // 加载 / 空 / 错误 状态 + &__loading, + &__empty, + &__error { + text-align: center; + padding: 48px 24px; + color: $text-secondary; + font-size: 14px; + } + + &__error { + color: #ff4d4f; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + } + + // 分页 + &__pagination { + display: flex; + justify-content: flex-end; + margin-top: 16px; + } +} diff --git a/frontend/admin-web/src/components/layout/Sidebar/Sidebar.tsx b/frontend/admin-web/src/components/layout/Sidebar/Sidebar.tsx index e31b1fc0..cbd45c5e 100644 --- a/frontend/admin-web/src/components/layout/Sidebar/Sidebar.tsx +++ b/frontend/admin-web/src/components/layout/Sidebar/Sidebar.tsx @@ -35,6 +35,8 @@ const topMenuItems: MenuItem[] = [ { 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' }, + // [2026-02-17] 新增:预种计划管理(3171 USDT/份预种开关 + 订单/持仓/合并查询) + { key: 'pre-planting', icon: '/images/Container3.svg', label: '预种管理', path: '/pre-planting' }, { key: 'maintenance', icon: '/images/Container6.svg', label: '系统维护', path: '/maintenance' }, { key: 'settings', icon: '/images/Container6.svg', label: '系统设置', path: '/settings' }, ];