300 lines
12 KiB
TypeScript
300 lines
12 KiB
TypeScript
/**
|
||
* 数据统计页面
|
||
* [2026-01-04] 更新:新增系统账户报表Tab
|
||
* [2026-01-06] 更新:认种统计改为真实数据,实现趋势图表
|
||
* 回滚方式:删除 SystemAccountsTab 导入及相关 mainTab state 和切换逻辑
|
||
*/
|
||
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import {
|
||
LineChart,
|
||
Line,
|
||
XAxis,
|
||
YAxis,
|
||
CartesianGrid,
|
||
Tooltip,
|
||
ResponsiveContainer,
|
||
Legend,
|
||
} from 'recharts';
|
||
import { PageContainer } from '@/components/layout';
|
||
import { cn } from '@/utils/helpers';
|
||
// [2026-01-04] 新增:系统账户报表Tab
|
||
import { SystemAccountsTab } from '@/components/features/system-account-report';
|
||
import { dashboardService } from '@/services/dashboardService';
|
||
import type { PlantingGlobalStats, PlantingTrendPeriod, PlantingTrendDataPoint } from '@/types';
|
||
import styles from './statistics.module.scss';
|
||
|
||
/**
|
||
* 格式化数字显示
|
||
*/
|
||
function formatNumber(num: number): string {
|
||
return num.toLocaleString('zh-CN');
|
||
}
|
||
|
||
/**
|
||
* 格式化金额显示(积分)
|
||
*/
|
||
function formatAmount(amount: string): string {
|
||
const num = parseFloat(amount);
|
||
if (isNaN(num)) return '0';
|
||
return num.toLocaleString('zh-CN', { maximumFractionDigits: 2 });
|
||
}
|
||
|
||
/**
|
||
* 格式化趋势图标签
|
||
*/
|
||
function formatTrendLabel(label: string, period: PlantingTrendPeriod): string {
|
||
if (!label) return '';
|
||
switch (period) {
|
||
case 'day':
|
||
// 2026-01-06 -> 01-06
|
||
return label.slice(5);
|
||
case 'week':
|
||
// 2026-W01 -> W01
|
||
return label.slice(5);
|
||
case 'month':
|
||
// 2026-01 -> 01月
|
||
return label.slice(5) + '月';
|
||
case 'quarter':
|
||
// 2026-Q1 -> Q1
|
||
return label.slice(5);
|
||
case 'year':
|
||
// 2026 -> 2026年
|
||
return label + '年';
|
||
default:
|
||
return label;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 数据统计页面
|
||
* [2026-01-04] 更新:新增系统账户报表Tab
|
||
* [2026-01-06] 更新:认种统计改为真实数据,实现趋势图表
|
||
*/
|
||
export default function StatisticsPage() {
|
||
// [2026-01-04] 新增:主Tab切换 - 数据统计 vs 系统账户
|
||
const [mainTab, setMainTab] = useState<'statistics' | 'system-accounts'>('statistics');
|
||
// 趋势图时间维度
|
||
const [trendPeriod, setTrendPeriod] = useState<PlantingTrendPeriod>('day');
|
||
|
||
// [2026-01-06] 新增:认种统计数据状态
|
||
const [plantingStats, setPlantingStats] = useState<PlantingGlobalStats | null>(null);
|
||
const [statsLoading, setStatsLoading] = useState(true);
|
||
const [statsError, setStatsError] = useState<string | null>(null);
|
||
|
||
// [2026-01-06] 新增:趋势数据状态
|
||
const [trendData, setTrendData] = useState<PlantingTrendDataPoint[]>([]);
|
||
const [trendLoading, setTrendLoading] = useState(true);
|
||
const [trendError, setTrendError] = useState<string | null>(null);
|
||
|
||
// [2026-01-06] 新增:获取认种统计数据
|
||
useEffect(() => {
|
||
async function fetchPlantingStats() {
|
||
try {
|
||
setStatsLoading(true);
|
||
setStatsError(null);
|
||
const response = await dashboardService.getPlantingStats();
|
||
if (response.code === 0 && response.data) {
|
||
setPlantingStats(response.data);
|
||
} else {
|
||
setStatsError(response.message || '获取统计数据失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取认种统计失败:', error);
|
||
setStatsError('获取统计数据失败');
|
||
} finally {
|
||
setStatsLoading(false);
|
||
}
|
||
}
|
||
fetchPlantingStats();
|
||
}, []);
|
||
|
||
// [2026-01-06] 新增:获取趋势数据
|
||
useEffect(() => {
|
||
async function fetchTrendData() {
|
||
try {
|
||
setTrendLoading(true);
|
||
setTrendError(null);
|
||
const response = await dashboardService.getPlantingTrendData(trendPeriod);
|
||
if (response.code === 0 && response.data) {
|
||
setTrendData(response.data);
|
||
} else {
|
||
setTrendError(response.message || '获取趋势数据失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取趋势数据失败:', error);
|
||
setTrendError('获取趋势数据失败');
|
||
} finally {
|
||
setTrendLoading(false);
|
||
}
|
||
}
|
||
fetchTrendData();
|
||
}, [trendPeriod]);
|
||
|
||
// 处理趋势数据用于图表显示
|
||
const chartData = trendData.map(item => ({
|
||
name: formatTrendLabel(item.label, trendPeriod),
|
||
认种棵数: item.treeCount,
|
||
订单数: item.orderCount,
|
||
}));
|
||
|
||
return (
|
||
<PageContainer title="数据统计">
|
||
<div className={styles.statistics}>
|
||
{/* 页面标题 */}
|
||
<h2 className={styles.statistics__title}>数据统计</h2>
|
||
|
||
{/* [2026-01-04] 新增:主Tab切换 */}
|
||
<div className={styles.statistics__mainTabs}>
|
||
<button
|
||
className={cn(styles.statistics__mainTab, mainTab === 'statistics' && styles['statistics__mainTab--active'])}
|
||
onClick={() => setMainTab('statistics')}
|
||
>
|
||
认种统计
|
||
</button>
|
||
<button
|
||
className={cn(styles.statistics__mainTab, mainTab === 'system-accounts' && styles['statistics__mainTab--active'])}
|
||
onClick={() => setMainTab('system-accounts')}
|
||
>
|
||
系统账户
|
||
</button>
|
||
</div>
|
||
|
||
{/* [2026-01-04] 新增:系统账户报表Tab内容 */}
|
||
{mainTab === 'system-accounts' && <SystemAccountsTab />}
|
||
|
||
{/* 认种统计内容 - 仅在 statistics tab 显示 */}
|
||
{mainTab === 'statistics' && (
|
||
<>
|
||
{/* 统计概览卡片 - [2026-01-06] 改为真实数据 */}
|
||
<section className={styles.statistics__overview}>
|
||
<div className={styles.statistics__overviewCard}>
|
||
<div className={styles.statistics__overviewLabel}>榴莲树认种总量</div>
|
||
{statsLoading ? (
|
||
<h1 className={styles.statistics__overviewValue}>加载中...</h1>
|
||
) : statsError ? (
|
||
<h1 className={styles.statistics__overviewValue}>--</h1>
|
||
) : (
|
||
<>
|
||
<h1 className={styles.statistics__overviewValue}>
|
||
{formatNumber(plantingStats?.totalTreeCount ?? 0)}
|
||
</h1>
|
||
<div className={styles.statistics__overviewSubValue}>
|
||
积分: {formatAmount(plantingStats?.totalAmount ?? '0')}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
<div className={styles.statistics__overviewCard}>
|
||
<div className={styles.statistics__overviewLabel}>今日认种数量</div>
|
||
{statsLoading ? (
|
||
<h1 className={styles.statistics__overviewValue}>加载中...</h1>
|
||
) : statsError ? (
|
||
<h1 className={styles.statistics__overviewValue}>--</h1>
|
||
) : (
|
||
<>
|
||
<h1 className={styles.statistics__overviewValue}>
|
||
{formatNumber(plantingStats?.todayStats?.treeCount ?? 0)}
|
||
</h1>
|
||
<div className={styles.statistics__overviewSubValue}>
|
||
积分: {formatAmount(plantingStats?.todayStats?.amount ?? '0')}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
<div className={styles.statistics__overviewCard}>
|
||
<div className={styles.statistics__overviewLabel}>本月认种数量</div>
|
||
{statsLoading ? (
|
||
<h1 className={styles.statistics__overviewValue}>加载中...</h1>
|
||
) : statsError ? (
|
||
<h1 className={styles.statistics__overviewValue}>--</h1>
|
||
) : (
|
||
<>
|
||
<h1 className={styles.statistics__overviewValue}>
|
||
{formatNumber(plantingStats?.monthStats?.treeCount ?? 0)}
|
||
</h1>
|
||
<div className={styles.statistics__overviewSubValue}>
|
||
积分: {formatAmount(plantingStats?.monthStats?.amount ?? '0')}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
</section>
|
||
|
||
{/* 榴莲树认种数量趋势 - [2026-01-06] 实现真实图表 */}
|
||
<section className={styles.statistics__card}>
|
||
<div className={styles.statistics__cardHeader}>
|
||
<b className={styles.statistics__cardTitle}>榴莲树认种数量趋势</b>
|
||
<div className={styles.statistics__periodTabs}>
|
||
{(['day', 'week', 'month', 'quarter', 'year'] as const).map((period) => (
|
||
<button
|
||
key={period}
|
||
className={cn(styles.statistics__periodTab, trendPeriod === period && styles['statistics__periodTab--active'])}
|
||
onClick={() => setTrendPeriod(period)}
|
||
>
|
||
{period === 'day' ? '日' : period === 'week' ? '周' : period === 'month' ? '月' : period === 'quarter' ? '季度' : '年度'}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div className={styles.statistics__chartArea}>
|
||
{trendLoading ? (
|
||
<div className={styles.statistics__chartLoading}>加载中...</div>
|
||
) : trendError ? (
|
||
<div className={styles.statistics__chartError}>{trendError}</div>
|
||
) : chartData.length === 0 ? (
|
||
<div className={styles.statistics__chartEmpty}>暂无数据</div>
|
||
) : (
|
||
<ResponsiveContainer width="100%" height={300}>
|
||
<LineChart data={chartData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
|
||
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
||
<XAxis
|
||
dataKey="name"
|
||
tick={{ fontSize: 12, fill: '#6b7280' }}
|
||
tickLine={{ stroke: '#e5e7eb' }}
|
||
axisLine={{ stroke: '#e5e7eb' }}
|
||
/>
|
||
<YAxis
|
||
tick={{ fontSize: 12, fill: '#6b7280' }}
|
||
tickLine={{ stroke: '#e5e7eb' }}
|
||
axisLine={{ stroke: '#e5e7eb' }}
|
||
allowDecimals={false}
|
||
/>
|
||
<Tooltip
|
||
contentStyle={{
|
||
backgroundColor: '#fff',
|
||
border: '1px solid #e5e7eb',
|
||
borderRadius: '8px',
|
||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
||
}}
|
||
/>
|
||
<Legend />
|
||
<Line
|
||
type="monotone"
|
||
dataKey="认种棵数"
|
||
stroke="#10b981"
|
||
strokeWidth={2}
|
||
dot={{ fill: '#10b981', strokeWidth: 2, r: 4 }}
|
||
activeDot={{ r: 6 }}
|
||
/>
|
||
<Line
|
||
type="monotone"
|
||
dataKey="订单数"
|
||
stroke="#3b82f6"
|
||
strokeWidth={2}
|
||
dot={{ fill: '#3b82f6', strokeWidth: 2, r: 4 }}
|
||
activeDot={{ r: 6 }}
|
||
/>
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
)}
|
||
</div>
|
||
</section>
|
||
</>
|
||
)}
|
||
</div>
|
||
</PageContainer>
|
||
);
|
||
}
|