# Mining Admin Web (挖矿管理后台) 开发指导 ## 1. 项目概述 ### 1.1 核心职责 Mining Admin Web 是挖矿系统的管理后台,供运营人员使用,用于配置管理、监控、数据查询等功能。 **主要功能:** - 仪表盘(实时数据监控) - 用户管理(算力/积分股查询) - 配置管理(系统参数配置) - 系统账户管理 - 报表与统计 - 审计日志查看 - 初始化任务管理 ### 1.2 技术栈 ``` 框架: Next.js 14 (App Router) 语言: TypeScript 状态管理: Zustand + Redux Toolkit (混合模式) UI 组件: Shadcn/ui + Tailwind CSS 图表: ECharts / Recharts 表格: TanStack Table 表单: React Hook Form + Zod 请求: TanStack Query + Axios ``` ### 1.3 架构模式 ``` Clean Architecture + Feature-Sliced Design ``` --- ## 2. 目录结构 ``` mining-admin-web/ ├── src/ │ ├── app/ # Next.js App Router │ │ ├── (auth)/ # 认证相关页面组 │ │ │ ├── login/ │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── (dashboard)/ # 主应用页面组 │ │ │ ├── dashboard/ │ │ │ │ └── page.tsx # 仪表盘首页 │ │ │ ├── users/ │ │ │ │ ├── page.tsx # 用户列表 │ │ │ │ └── [accountSequence]/ │ │ │ │ └── page.tsx # 用户详情 │ │ │ ├── configs/ │ │ │ │ └── page.tsx # 配置管理 │ │ │ ├── system-accounts/ │ │ │ │ └── page.tsx # 系统账户 │ │ │ ├── reports/ │ │ │ │ └── page.tsx # 报表 │ │ │ ├── audit-logs/ │ │ │ │ └── page.tsx # 审计日志 │ │ │ ├── initialization/ │ │ │ │ └── page.tsx # 初始化任务 │ │ │ └── layout.tsx # 主布局(侧边栏+顶栏) │ │ ├── api/ # API Routes (BFF) │ │ │ └── [...path]/ │ │ │ └── route.ts # 代理到后端服务 │ │ ├── layout.tsx # 根布局 │ │ ├── page.tsx # 根页面(重定向) │ │ └── globals.css │ │ │ ├── components/ # 共享组件 │ │ ├── ui/ # 基础UI组件 (Shadcn) │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── dialog.tsx │ │ │ ├── input.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── toast.tsx │ │ │ └── ... │ │ ├── charts/ # 图表组件 │ │ │ ├── price-chart.tsx # 价格K线图 │ │ │ ├── contribution-chart.tsx # 算力趋势图 │ │ │ ├── distribution-chart.tsx # 分配统计图 │ │ │ └── realtime-gauge.tsx # 实时仪表盘 │ │ ├── tables/ # 表格组件 │ │ │ ├── data-table.tsx # 通用数据表格 │ │ │ ├── user-table.tsx │ │ │ ├── config-table.tsx │ │ │ └── audit-log-table.tsx │ │ ├── forms/ # 表单组件 │ │ │ ├── config-form.tsx │ │ │ └── system-account-form.tsx │ │ └── layout/ # 布局组件 │ │ ├── sidebar.tsx │ │ ├── header.tsx │ │ ├── breadcrumb.tsx │ │ └── page-header.tsx │ │ │ ├── features/ # 功能模块 (Feature-Sliced) │ │ ├── dashboard/ │ │ │ ├── components/ │ │ │ │ ├── stats-cards.tsx │ │ │ │ ├── realtime-panel.tsx │ │ │ │ ├── price-overview.tsx │ │ │ │ └── activity-feed.tsx │ │ │ ├── hooks/ │ │ │ │ ├── use-dashboard-stats.ts │ │ │ │ └── use-realtime-data.ts │ │ │ ├── api/ │ │ │ │ └── dashboard.api.ts │ │ │ └── store/ │ │ │ └── dashboard.slice.ts # RTK Slice │ │ │ │ │ ├── users/ │ │ │ ├── components/ │ │ │ │ ├── user-search.tsx │ │ │ │ ├── user-detail-card.tsx │ │ │ │ ├── contribution-breakdown.tsx │ │ │ │ ├── mining-records-list.tsx │ │ │ │ └── trade-orders-list.tsx │ │ │ ├── hooks/ │ │ │ │ ├── use-users.ts │ │ │ │ └── use-user-detail.ts │ │ │ ├── api/ │ │ │ │ └── users.api.ts │ │ │ └── store/ │ │ │ └── users.slice.ts │ │ │ │ │ ├── configs/ │ │ │ ├── components/ │ │ │ │ ├── config-list.tsx │ │ │ │ ├── config-edit-dialog.tsx │ │ │ │ └── transfer-switch.tsx │ │ │ ├── hooks/ │ │ │ │ └── use-configs.ts │ │ │ ├── api/ │ │ │ │ └── configs.api.ts │ │ │ └── store/ │ │ │ └── configs.slice.ts │ │ │ │ │ ├── system-accounts/ │ │ │ ├── components/ │ │ │ ├── hooks/ │ │ │ ├── api/ │ │ │ └── store/ │ │ │ │ │ ├── reports/ │ │ │ ├── components/ │ │ │ │ ├── report-filters.tsx │ │ │ │ ├── contribution-report.tsx │ │ │ │ ├── mining-report.tsx │ │ │ │ └── trading-report.tsx │ │ │ ├── hooks/ │ │ │ ├── api/ │ │ │ └── store/ │ │ │ │ │ └── audit-logs/ │ │ ├── components/ │ │ ├── hooks/ │ │ ├── api/ │ │ └── store/ │ │ │ ├── lib/ # 工具库 │ │ ├── api/ │ │ │ ├── client.ts # Axios 实例 │ │ │ ├── interceptors.ts # 请求拦截器 │ │ │ └── types.ts # API 类型 │ │ ├── utils/ │ │ │ ├── format.ts # 格式化工具 │ │ │ ├── decimal.ts # 高精度计算 │ │ │ └── date.ts # 日期处理 │ │ ├── hooks/ │ │ │ ├── use-auth.ts │ │ │ ├── use-toast.ts │ │ │ └── use-confirmation.ts │ │ └── constants/ │ │ ├── routes.ts │ │ └── config.ts │ │ │ ├── store/ # 全局状态管理 │ │ ├── index.ts # Store 配置 │ │ ├── slices/ │ │ │ ├── auth.slice.ts # RTK: 认证状态 │ │ │ ├── ui.slice.ts # RTK: UI状态 │ │ │ └── realtime.slice.ts # RTK: 实时数据 │ │ ├── middleware/ │ │ │ └── logger.ts │ │ └── zustand/ │ │ ├── use-sidebar.ts # Zustand: 侧边栏状态 │ │ └── use-theme.ts # Zustand: 主题状态 │ │ │ └── types/ # 全局类型定义 │ ├── api.ts │ ├── user.ts │ ├── config.ts │ ├── dashboard.ts │ └── common.ts │ ├── public/ │ └── images/ │ ├── .env.local # 环境变量 ├── .env.production ├── next.config.js ├── tailwind.config.js ├── tsconfig.json ├── package.json └── README.md ``` --- ## 3. 状态管理策略 ### 3.1 混合模式:Zustand + Redux Toolkit ``` ┌─────────────────────────────────────────────────────────────┐ │ 状态管理分层策略 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Redux Toolkit (RTK) │ │ ├── 复杂业务状态(用户列表、配置、报表等) │ │ ├── 需要 DevTools 调试的状态 │ │ ├── 需要中间件处理的状态 │ │ └── 跨页面共享的业务数据 │ │ │ │ Zustand │ │ ├── 简单UI状态(侧边栏、模态框、主题) │ │ ├── 临时状态(表单草稿、筛选条件) │ │ └── 组件级局部状态 │ │ │ │ TanStack Query │ │ ├── 服务端数据缓存 │ │ ├── 自动重新获取 │ │ └── 乐观更新 │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### 3.2 RTK Slice 示例 ```typescript // store/slices/configs.slice.ts import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import { configsApi } from '@/features/configs/api/configs.api'; import type { SystemConfig } from '@/types/config'; interface ConfigsState { items: SystemConfig[]; loading: boolean; error: string | null; selectedKey: string | null; } const initialState: ConfigsState = { items: [], loading: false, error: null, selectedKey: null, }; // Async Thunks export const fetchConfigs = createAsyncThunk( 'configs/fetchConfigs', async (_, { rejectWithValue }) => { try { const response = await configsApi.getAll(); return response.data; } catch (error: any) { return rejectWithValue(error.message); } } ); export const updateConfig = createAsyncThunk( 'configs/updateConfig', async ({ key, value }: { key: string; value: string }, { rejectWithValue }) => { try { const response = await configsApi.update(key, value); return response.data; } catch (error: any) { return rejectWithValue(error.message); } } ); // Slice const configsSlice = createSlice({ name: 'configs', initialState, reducers: { setSelectedKey: (state, action: PayloadAction) => { state.selectedKey = action.payload; }, clearError: (state) => { state.error = null; }, }, extraReducers: (builder) => { builder .addCase(fetchConfigs.pending, (state) => { state.loading = true; state.error = null; }) .addCase(fetchConfigs.fulfilled, (state, action) => { state.loading = false; state.items = action.payload; }) .addCase(fetchConfigs.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; }) .addCase(updateConfig.fulfilled, (state, action) => { const index = state.items.findIndex( (item) => item.configKey === action.payload.configKey ); if (index !== -1) { state.items[index] = action.payload; } }); }, }); export const { setSelectedKey, clearError } = configsSlice.actions; export default configsSlice.reducer; ``` ### 3.3 Zustand Store 示例 ```typescript // store/zustand/use-sidebar.ts import { create } from 'zustand'; import { persist } from 'zustand/middleware'; interface SidebarState { isCollapsed: boolean; toggle: () => void; setCollapsed: (collapsed: boolean) => void; } export const useSidebar = create()( persist( (set) => ({ isCollapsed: false, toggle: () => set((state) => ({ isCollapsed: !state.isCollapsed })), setCollapsed: (collapsed) => set({ isCollapsed: collapsed }), }), { name: 'sidebar-state', } ) ); ``` ### 3.4 TanStack Query 示例 ```typescript // features/dashboard/hooks/use-dashboard-stats.ts import { useQuery } from '@tanstack/react-query'; import { dashboardApi } from '../api/dashboard.api'; export function useDashboardStats() { return useQuery({ queryKey: ['dashboard', 'stats'], queryFn: () => dashboardApi.getStats(), refetchInterval: 30000, // 30秒刷新 staleTime: 10000, // 10秒内不重新获取 }); } export function useRealtimeData() { return useQuery({ queryKey: ['dashboard', 'realtime'], queryFn: () => dashboardApi.getRealtimeData(), refetchInterval: 5000, // 5秒刷新 staleTime: 3000, }); } ``` --- ## 4. 页面开发示例 ### 4.1 仪表盘页面 ```typescript // app/(dashboard)/dashboard/page.tsx 'use client'; import { StatsCards } from '@/features/dashboard/components/stats-cards'; import { RealtimePanel } from '@/features/dashboard/components/realtime-panel'; import { PriceOverview } from '@/features/dashboard/components/price-overview'; import { ActivityFeed } from '@/features/dashboard/components/activity-feed'; import { PageHeader } from '@/components/layout/page-header'; export default function DashboardPage() { return (
{/* 统计卡片 */} {/* 主要内容区 */}
{/* 价格概览 */}
{/* 实时数据面板 */}
{/* 活动动态 */}
); } ``` ### 4.2 统计卡片组件 ```typescript // features/dashboard/components/stats-cards.tsx 'use client'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { useDashboardStats } from '../hooks/use-dashboard-stats'; import { formatNumber, formatDecimal } from '@/lib/utils/format'; import { TrendingUp, Users, Coins, Activity } from 'lucide-react'; export function StatsCards() { const { data: stats, isLoading } = useDashboardStats(); if (isLoading) { return ; } const cards = [ { title: '当前价格', value: formatDecimal(stats?.currentPrice, 8), unit: '绿积分/股', icon: TrendingUp, trend: '+12.5%', trendUp: true, }, { title: '全网算力', value: formatNumber(stats?.networkEffectiveContribution), icon: Activity, }, { title: '已分配积分股', value: formatNumber(stats?.totalDistributed), icon: Coins, }, { title: '认种用户数', value: formatNumber(stats?.adoptedUsers), icon: Users, }, ]; return (
{cards.map((card) => ( {card.title}
{card.value}
{card.unit && (

{card.unit}

)} {card.trend && (

{card.trend} 较昨日

)}
))}
); } ``` ### 4.3 用户详情页面 ```typescript // app/(dashboard)/users/[accountSequence]/page.tsx 'use client'; import { useParams } from 'next/navigation'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { PageHeader } from '@/components/layout/page-header'; import { UserDetailCard } from '@/features/users/components/user-detail-card'; import { ContributionBreakdown } from '@/features/users/components/contribution-breakdown'; import { MiningRecordsList } from '@/features/users/components/mining-records-list'; import { TradeOrdersList } from '@/features/users/components/trade-orders-list'; import { useUserDetail } from '@/features/users/hooks/use-user-detail'; export default function UserDetailPage() { const params = useParams(); const accountSequence = params.accountSequence as string; const { data: user, isLoading } = useUserDetail(accountSequence); if (isLoading) { return ; } return (
{/* 用户基本信息卡片 */} {/* 详细信息标签页 */} 算力明细 挖矿记录 交易记录
); } ``` --- ## 5. API 客户端 ### 5.1 Axios 实例配置 ```typescript // lib/api/client.ts import axios from 'axios'; import { getSession } from 'next-auth/react'; export const apiClient = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL || '/api', timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); // 请求拦截器 apiClient.interceptors.request.use( async (config) => { const session = await getSession(); if (session?.accessToken) { config.headers.Authorization = `Bearer ${session.accessToken}`; } return config; }, (error) => Promise.reject(error) ); // 响应拦截器 apiClient.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { // 处理未授权 window.location.href = '/login'; } return Promise.reject(error); } ); ``` ### 5.2 功能模块 API ```typescript // features/dashboard/api/dashboard.api.ts import { apiClient } from '@/lib/api/client'; import type { DashboardStats, RealtimeData } from '@/types/dashboard'; export const dashboardApi = { getStats: async (): Promise => { const response = await apiClient.get('/admin/dashboard'); return response.data; }, getRealtimeData: async (): Promise => { const response = await apiClient.get('/admin/dashboard/realtime'); return response.data; }, getChartData: async (type: string, period: string) => { const response = await apiClient.get('/admin/dashboard/charts', { params: { type, period }, }); return response.data; }, }; ``` --- ## 6. 图表组件 ### 6.1 价格K线图 ```typescript // components/charts/price-chart.tsx 'use client'; import { useEffect, useRef } from 'react'; import * as echarts from 'echarts'; import type { KlineData } from '@/types/dashboard'; interface PriceChartProps { data: KlineData[]; period: string; } export function PriceChart({ data, period }: PriceChartProps) { const chartRef = useRef(null); useEffect(() => { if (!chartRef.current || !data.length) return; const chart = echarts.init(chartRef.current); const option: echarts.EChartsOption = { tooltip: { trigger: 'axis', axisPointer: { type: 'cross' }, }, xAxis: { type: 'category', data: data.map((d) => d.time), boundaryGap: false, }, yAxis: { type: 'value', scale: true, splitLine: { show: true }, }, series: [ { name: '价格', type: 'candlestick', data: data.map((d) => [d.open, d.close, d.low, d.high]), itemStyle: { color: '#ef4444', // 上涨 color0: '#22c55e', // 下跌 borderColor: '#ef4444', borderColor0: '#22c55e', }, }, { name: '成交量', type: 'bar', xAxisIndex: 0, yAxisIndex: 1, data: data.map((d) => d.volume), itemStyle: { color: (params: any) => { const item = data[params.dataIndex]; return item.close >= item.open ? '#ef4444' : '#22c55e'; }, }, }, ], grid: [ { left: '10%', right: '10%', height: '50%' }, { left: '10%', right: '10%', top: '65%', height: '20%' }, ], dataZoom: [ { type: 'inside', xAxisIndex: [0, 1], start: 50, end: 100 }, { show: true, xAxisIndex: [0, 1], type: 'slider', bottom: 10 }, ], }; chart.setOption(option); const handleResize = () => chart.resize(); window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); chart.dispose(); }; }, [data, period]); return
; } ``` --- ## 7. 环境变量 ```bash # .env.local NEXT_PUBLIC_API_URL=http://localhost:3023 NEXT_PUBLIC_WS_URL=ws://localhost:3023 # 认证 NEXTAUTH_URL=http://localhost:3100 NEXTAUTH_SECRET=your-secret-key # 其他 NEXT_PUBLIC_APP_NAME="挖矿管理后台" ``` --- ## 8. 关键注意事项 ### 8.1 数据精度 - 使用 `Decimal.js` 或字符串处理大数值 - 显示时根据场景选择合适的小数位数 - 价格显示8位小数,数量显示4位小数 ### 8.2 实时数据 - 使用 TanStack Query 的 `refetchInterval` 实现轮询 - 考虑使用 WebSocket 获取实时推送 - 注意内存泄漏,组件卸载时清理订阅 ### 8.3 性能优化 - 大表格使用虚拟滚动 - 图表数据分页加载 - 使用 `React.memo` 优化重渲染 ### 8.4 错误处理 - 全局错误边界捕获异常 - API 错误统一 toast 提示 - 表单验证使用 Zod --- ## 9. 开发检查清单 - [ ] 配置 Next.js 项目 - [ ] 集成 Shadcn/ui 组件库 - [ ] 配置 Redux Toolkit Store - [ ] 配置 Zustand Stores - [ ] 配置 TanStack Query - [ ] 实现登录认证 - [ ] 实现仪表盘页面 - [ ] 实现用户列表/详情页面 - [ ] 实现配置管理页面 - [ ] 实现系统账户页面 - [ ] 实现报表页面 - [ ] 实现审计日志页面 - [ ] 实现K线图表 - [ ] 响应式布局适配 - [ ] 编写测试 --- ## 10. 启动命令 ```bash # 安装依赖 npm install # 开发环境 npm run dev # 构建生产版本 npm run build # 启动生产版本 npm run start # 代码检查 npm run lint # 类型检查 npm run type-check ```