diff --git a/frontend/admin-web/src/hooks/index.ts b/frontend/admin-web/src/hooks/index.ts index d6135e64..73975a93 100644 --- a/frontend/admin-web/src/hooks/index.ts +++ b/frontend/admin-web/src/hooks/index.ts @@ -5,3 +5,5 @@ export * from './useUsers'; export * from './useUserDetailPage'; export * from './useAuthorizations'; export * from './useSystemWithdrawal'; +// [2026-02-17] 预种计划管理 +export * from './usePrePlanting'; diff --git a/frontend/admin-web/src/hooks/usePrePlanting.ts b/frontend/admin-web/src/hooks/usePrePlanting.ts new file mode 100644 index 00000000..70b11ef6 --- /dev/null +++ b/frontend/admin-web/src/hooks/usePrePlanting.ts @@ -0,0 +1,107 @@ +/** + * [2026-02-17] 预种计划管理 React Query Hooks + * + * 为 admin-web 预种管理页面提供数据获取 hooks。 + * 遵循项目的 React Query 模式: + * - Query key factory(层级化、参数化) + * - 每个查询对应一个 hook + * - useMutation 用于开关切换等写操作 + * + * === 使用方式 === + * import { usePrePlantingConfig, usePrePlantingStats, ... } from '@/hooks'; + */ + +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { + prePlantingService, + type PrePlantingListParams, +} from '@/services/prePlantingService'; + +// ============================================ +// Query Key Factory +// ============================================ + +export const prePlantingKeys = { + all: ['pre-planting'] as const, + config: () => [...prePlantingKeys.all, 'config'] as const, + stats: () => [...prePlantingKeys.all, 'stats'] as const, + orders: (params: PrePlantingListParams) => + [...prePlantingKeys.all, 'orders', params] as const, + positions: (params: PrePlantingListParams) => + [...prePlantingKeys.all, 'positions', params] as const, + merges: (params: PrePlantingListParams) => + [...prePlantingKeys.all, 'merges', params] as const, +}; + +// ============================================ +// Query Hooks +// ============================================ + +/** 获取预种开关配置 */ +export function usePrePlantingConfig() { + return useQuery({ + queryKey: prePlantingKeys.config(), + queryFn: () => prePlantingService.getConfig(), + staleTime: 30 * 1000, + gcTime: 5 * 60 * 1000, + }); +} + +/** 获取预种统计汇总 */ +export function usePrePlantingStats() { + return useQuery({ + queryKey: prePlantingKeys.stats(), + queryFn: () => prePlantingService.getStats(), + staleTime: 30 * 1000, + gcTime: 5 * 60 * 1000, + }); +} + +/** 获取预种订单列表 */ +export function usePrePlantingOrders(params: PrePlantingListParams = {}) { + return useQuery({ + queryKey: prePlantingKeys.orders(params), + queryFn: () => prePlantingService.getOrders(params), + staleTime: 30 * 1000, + gcTime: 5 * 60 * 1000, + }); +} + +/** 获取预种持仓列表 */ +export function usePrePlantingPositions(params: PrePlantingListParams = {}) { + return useQuery({ + queryKey: prePlantingKeys.positions(params), + queryFn: () => prePlantingService.getPositions(params), + staleTime: 30 * 1000, + gcTime: 5 * 60 * 1000, + }); +} + +/** 获取合并记录列表 */ +export function usePrePlantingMerges(params: PrePlantingListParams = {}) { + return useQuery({ + queryKey: prePlantingKeys.merges(params), + queryFn: () => prePlantingService.getMerges(params), + staleTime: 30 * 1000, + gcTime: 5 * 60 * 1000, + }); +} + +// ============================================ +// Mutation Hooks +// ============================================ + +/** 切换预种开关 */ +export function useTogglePrePlantingConfig() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (isActive: boolean) => + prePlantingService.toggleConfig(isActive), + onSuccess: () => { + // 开关切换后刷新配置和统计 + queryClient.invalidateQueries({ queryKey: prePlantingKeys.config() }); + queryClient.invalidateQueries({ queryKey: prePlantingKeys.stats() }); + }, + }); +} diff --git a/frontend/admin-web/src/infrastructure/api/endpoints.ts b/frontend/admin-web/src/infrastructure/api/endpoints.ts index 7f23381a..6af9dab4 100644 --- a/frontend/admin-web/src/infrastructure/api/endpoints.ts +++ b/frontend/admin-web/src/infrastructure/api/endpoints.ts @@ -288,4 +288,20 @@ export const API_ENDPOINTS = { // 批量下载任务状态 BATCH_DOWNLOAD_STATUS: (taskNo: string) => `/v1/admin/contracts/batch-download/${taskNo}`, }, + // [2026-02-17] 新增:预种计划管理 (admin-service / PrePlantingAdminModule) + // 3171 USDT/份预种,累计 5 份合成 1 棵树 + // 开关管理 + 预种订单/持仓/合并记录查询 + PRE_PLANTING: { + // 预种开关配置 + CONFIG: '/v1/admin/pre-planting/config', + TOGGLE: '/v1/admin/pre-planting/config/toggle', + // 预种订单列表(管理员视角,可按用户/状态过滤) + ORDERS: '/v1/admin/pre-planting/orders', + // 预种持仓列表(管理员视角) + POSITIONS: '/v1/admin/pre-planting/positions', + // 合并记录列表(管理员视角) + MERGES: '/v1/admin/pre-planting/merges', + // 预种统计汇总(总份数、总金额、合并树数等) + STATS: '/v1/admin/pre-planting/stats', + }, } as const; diff --git a/frontend/admin-web/src/services/prePlantingService.ts b/frontend/admin-web/src/services/prePlantingService.ts new file mode 100644 index 00000000..bf9f1dc7 --- /dev/null +++ b/frontend/admin-web/src/services/prePlantingService.ts @@ -0,0 +1,163 @@ +/** + * [2026-02-17] 预种计划管理服务 + * + * 管理员端的预种计划 API 调用服务。 + * 提供预种开关管理、订单/持仓/合并记录查询、统计汇总等功能。 + * + * === API 端点 === + * 所有端点走 admin-service 的 PrePlantingAdminModule: + * - GET /v1/admin/pre-planting/config 获取预种开关配置 + * - PUT /v1/admin/pre-planting/config/toggle 切换预种开关 + * - GET /v1/admin/pre-planting/orders 预种订单列表 + * - GET /v1/admin/pre-planting/positions 预种持仓列表 + * - GET /v1/admin/pre-planting/merges 合并记录列表 + * - GET /v1/admin/pre-planting/stats 统计汇总 + * + * === 与现有服务的关系 === + * 完全独立于现有认种管理。不涉及 planting_orders 或 CONTRACTS 等现有端点。 + */ + +import apiClient from '@/infrastructure/api/client'; +import { API_ENDPOINTS } from '@/infrastructure/api/endpoints'; + +// ============================================ +// 类型定义 +// ============================================ + +/** 预种开关配置 */ +export interface PrePlantingConfig { + isActive: boolean; + activatedAt: string | null; + updatedAt: string; + updatedBy: string | null; +} + +/** 预种统计汇总 */ +export interface PrePlantingStats { + totalOrders: number; // 总订单数 + totalPortions: number; // 总份数 + totalAmount: number; // 总金额(USDT) + totalMerges: number; // 合并次数 + totalTreesMerged: number; // 合成树数 + totalUsers: number; // 参与用户数 + pendingContracts: number; // 待签约合并数 +} + +/** 预种订单(管理员视角) */ +export interface PrePlantingAdminOrder { + id: string; + orderNo: string; + userId: string; + accountSequence: string; + portionCount: number; + pricePerPortion: number; + totalAmount: number; + provinceCode: string; + cityCode: string; + status: 'CREATED' | 'PAID' | 'MERGED'; + mergedToMergeId: string | null; + createdAt: string; + paidAt: string | null; + mergedAt: string | null; +} + +/** 预种持仓(管理员视角) */ +export interface PrePlantingAdminPosition { + id: string; + userId: string; + accountSequence: string; + totalPortions: number; + availablePortions: number; + mergedPortions: number; + totalTreesMerged: number; + provinceCode: string | null; + cityCode: string | null; + firstPurchaseAt: string | null; + updatedAt: string; +} + +/** 预种合并记录(管理员视角) */ +export interface PrePlantingAdminMerge { + id: string; + mergeNo: string; + userId: string; + accountSequence: string; + sourceOrderNos: string[]; + treeCount: number; + contractStatus: 'PENDING' | 'SIGNED' | 'EXPIRED'; + contractSignedAt: string | null; + miningEnabledAt: string | null; + selectedProvince: string | null; + selectedCity: string | null; + mergedAt: string; +} + +/** 分页列表请求参数 */ +export interface PrePlantingListParams { + page?: number; + pageSize?: number; + status?: string; + accountSequence?: string; + keyword?: string; +} + +/** 分页列表响应 */ +export interface PaginatedResponse { + items: T[]; + total: number; + page: number; + pageSize: number; +} + +// ============================================ +// 预种计划管理服务 +// ============================================ + +export const prePlantingService = { + /** + * 获取预种开关配置 + */ + async getConfig(): Promise { + return apiClient.get(API_ENDPOINTS.PRE_PLANTING.CONFIG); + }, + + /** + * 切换预种开关(开启/关闭) + * + * 开关仅控制"能否发起新购买",不影响已完成的业务流程。 + * 关闭后已有未凑满份额的用户仍可继续购买至 5 的倍数。 + */ + async toggleConfig(isActive: boolean): Promise { + return apiClient.put(API_ENDPOINTS.PRE_PLANTING.TOGGLE, { isActive }); + }, + + /** + * 获取预种统计汇总 + */ + async getStats(): Promise { + return apiClient.get(API_ENDPOINTS.PRE_PLANTING.STATS); + }, + + /** + * 获取预种订单列表(管理员视角) + */ + async getOrders(params: PrePlantingListParams = {}): Promise> { + return apiClient.get(API_ENDPOINTS.PRE_PLANTING.ORDERS, { params }); + }, + + /** + * 获取预种持仓列表(管理员视角) + */ + async getPositions(params: PrePlantingListParams = {}): Promise> { + return apiClient.get(API_ENDPOINTS.PRE_PLANTING.POSITIONS, { params }); + }, + + /** + * 获取合并记录列表(管理员视角) + */ + async getMerges(params: PrePlantingListParams = {}): Promise> { + return apiClient.get(API_ENDPOINTS.PRE_PLANTING.MERGES, { params }); + }, +}; + +export default prePlantingService;