diff --git a/crop-x/src/app/(app)/land-information/archive/context/components/FilterPanel.tsx b/crop-x/src/app/(app)/land-information/archive/context/components/FilterPanel.tsx new file mode 100644 index 0000000..4d348d0 --- /dev/null +++ b/crop-x/src/app/(app)/land-information/archive/context/components/FilterPanel.tsx @@ -0,0 +1,167 @@ +'use client'; + +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { Filter, Search, Trash2 } from 'lucide-react'; +import { FilterCondition, SoilType, PlantingMode, LandTag } from './landStatisticsReducer'; + +interface FilterPanelProps { + filters: FilterCondition; + soilTypes: SoilType[]; + plantingModes: PlantingMode[]; + tags: LandTag[]; + onFilterChange: (key: keyof FilterCondition, value: any) => void; + onToggleArrayFilter: (key: 'soilTypes' | 'plantingModes' | 'tags', value: string) => void; + onClearFilters: () => void; + onExecuteQuery: () => void; +} + +export function FilterPanel({ + filters, + soilTypes, + plantingModes, + tags, + onFilterChange, + onToggleArrayFilter, + onClearFilters, + onExecuteQuery, +}: FilterPanelProps) { + return ( + +
+ +

筛选条件

+
+ +
+ {/* 关键词搜索 */} +
+ +
+ + onFilterChange('keyword', e.target.value)} + className="pl-10" + /> +
+
+ + {/* 土壤类型 */} +
+ +
+ {soilTypes.map((type) => ( + onToggleArrayFilter('soilTypes', type.key)} + > +
+ {type.name} + + ))} +
+
+ + {/* 种植模式 */} +
+ +
+ {plantingModes.map((mode) => ( + onToggleArrayFilter('plantingModes', mode.key)} + > + {mode.emoji} + {mode.name} + + ))} +
+
+ + {/* 标签 */} +
+ +
+ {tags.map((tag) => ( + onToggleArrayFilter('tags', tag.name)} + > + {tag.name} + + ))} +
+
+ + {/* 面积范围 */} +
+
+ + onFilterChange('minArea', e.target.value)} + /> +
+
+ + onFilterChange('maxArea', e.target.value)} + /> +
+
+ + {/* 操作按钮 */} +
+ + +
+
+ + ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/land-information/archive/context/components/StatisticsResults.tsx b/crop-x/src/app/(app)/land-information/archive/context/components/StatisticsResults.tsx new file mode 100644 index 0000000..ba7c14e --- /dev/null +++ b/crop-x/src/app/(app)/land-information/archive/context/components/StatisticsResults.tsx @@ -0,0 +1,267 @@ +'use client'; + +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { + BarChart3, + Download, + BarChart, + PieChart, +} from 'lucide-react'; +import { StatisticsResult } from './landStatisticsReducer'; +import { + BarChart as RechartsBarChart, + Bar, + PieChart as RechartsPieChart, + Pie, + Cell, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; + +const COLORS = ['#22c55e', '#3b82f6', '#f59e0b', '#a855f7', '#ef4444', '#14b8a6', '#f97316', '#8b5cf6']; + +interface StatisticsResultsProps { + statistics: StatisticsResult; + chartType: 'bar' | 'pie'; + onChartTypeChange: (type: 'bar' | 'pie') => void; + onExportData: () => void; +} + +export function StatisticsResults({ + statistics, + chartType, + onChartTypeChange, + onExportData, +}: StatisticsResultsProps) { + return ( + <> + {/* 基础统计 */} + +
+
+ +

统计结果

+
+ +
+ +
+ +
地块总数
+
{statistics.totalCount}
+
+ +
总面积
+
{statistics.totalArea.toFixed(2)} 亩
+
+ +
平均面积
+
{statistics.avgArea.toFixed(2)} 亩
+
+ +
最大面积
+
{statistics.maxArea.toFixed(2)} 亩
+
+ +
最小面积
+
{statistics.minArea.toFixed(2)} 亩
+
+
+
+ + {/* 图表选择 */} +
+ + +
+ + {/* 土壤类型分布 */} + +

土壤类型分布

+ {chartType === 'bar' ? ( + + + + + + + + + + + + ) : ( +
+
+

按地块数量

+ + + + {statistics.soilTypeDistribution.map((entry, index) => ( + + ))} + + + + + +
+
+

按面积

+ + + + {statistics.soilTypeDistribution.map((entry, index) => ( + + ))} + + + + + +
+
+ )} +
+ + {/* 种植模式分布 */} + +

种植模式分布

+ {chartType === 'bar' ? ( + + + + + + + + + + + + ) : ( +
+
+

按地块数量

+ + + + {statistics.plantingModeDistribution.map((entry, index) => ( + + ))} + + + + + +
+
+

按面积

+ + + + {statistics.plantingModeDistribution.map((entry, index) => ( + + ))} + + + + + +
+
+ )} +
+ + {/* 标签分布 */} + {statistics.tagDistribution.length > 0 && ( + +

标签分布

+
+ {statistics.tagDistribution.map((tag) => ( + + + {tag.name} + +
+
{tag.count}
+
个地块
+
+
+ ))} +
+
+ )} + + ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/land-information/archive/context/components/UsageExamples.tsx b/crop-x/src/app/(app)/land-information/archive/context/components/UsageExamples.tsx new file mode 100644 index 0000000..261e583 --- /dev/null +++ b/crop-x/src/app/(app)/land-information/archive/context/components/UsageExamples.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { Card } from '@/components/ui/card'; + +export function UsageExamples() { + return ( + +

+ 💡 + 使用示例 +

+
    +
  • • 统计所有沙土且面积大于50亩的地块:选择"沙土",设置最小面积为50
  • +
  • • 统计有机种植的露地地块:选择"露地"种植模式,选择"有机种植"标签
  • +
  • • 统计50-100亩的大棚地块:选择"大棚",设置面积范围50-100
  • +
  • • 多条件组合:可同时选择多个土壤类型、种植模式和标签
  • +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/land-information/archive/context/components/landStatisticsReducer.tsx b/crop-x/src/app/(app)/land-information/archive/context/components/landStatisticsReducer.tsx new file mode 100644 index 0000000..9fe1d1d --- /dev/null +++ b/crop-x/src/app/(app)/land-information/archive/context/components/landStatisticsReducer.tsx @@ -0,0 +1,187 @@ +'use client'; + +import { ReactNode } from 'react'; + +// 类型定义 +export interface LandField { + id: string; + code: string; + name: string; + area: number; + location: string; + soilType: string; + plantingMode: string; + tags: string[]; + status: 'active' | 'inactive' | 'pending'; + description?: string; + createdAt: string; + updatedAt: string; +} + +export interface LandTag { + id: string; + name: string; + color: string; + description?: string; + createdAt: string; +} + +export interface SoilType { + id: string; + key: string; + name: string; + color: string; +} + +export interface PlantingMode { + id: string; + key: string; + name: string; + emoji: string; +} + +export interface FilterCondition { + soilTypes: string[]; + plantingModes: string[]; + tags: string[]; + minArea: string; + maxArea: string; + keyword: string; +} + +export interface StatisticsResult { + totalCount: number; + totalArea: number; + avgArea: number; + maxArea: number; + minArea: number; + soilTypeDistribution: { name: string; count: number; area: number; color: string }[]; + plantingModeDistribution: { name: string; count: number; area: number; emoji: string }[]; + tagDistribution: { name: string; count: number; color: string }[]; +} + +export interface LandStatisticsState { + fields: LandField[]; + tags: LandTag[]; + soilTypes: SoilType[]; + plantingModes: PlantingMode[]; + filters: FilterCondition; + statistics: StatisticsResult | null; + chartType: 'bar' | 'pie'; + loading: boolean; +} + +// 初始状态 +export const initialState: LandStatisticsState = { + fields: [], + tags: [], + soilTypes: [], + plantingModes: [], + filters: { + soilTypes: [], + plantingModes: [], + tags: [], + minArea: '', + maxArea: '', + keyword: '', + }, + statistics: null, + chartType: 'bar', + loading: false, +}; + +// Action 类型定义 +export type LandStatisticsAction = + | { type: 'SET_FIELDS'; payload: LandField[] } + | { type: 'SET_TAGS'; payload: LandTag[] } + | { type: 'SET_SOIL_TYPES'; payload: SoilType[] } + | { type: 'SET_PLANTING_MODES'; payload: PlantingMode[] } + | { type: 'UPDATE_FILTER'; payload: { key: keyof FilterCondition; value: any } } + | { type: 'TOGGLE_ARRAY_FILTER'; payload: { key: 'soilTypes' | 'plantingModes' | 'tags'; value: string } } + | { type: 'CLEAR_FILTERS' } + | { type: 'SET_STATISTICS'; payload: StatisticsResult | null } + | { type: 'SET_CHART_TYPE'; payload: 'bar' | 'pie' } + | { type: 'SET_LOADING'; payload: boolean }; + +// Reducer 函数 +export function LandStatisticsReducer( + state: LandStatisticsState, + action: LandStatisticsAction +): LandStatisticsState { + switch (action.type) { + case 'SET_FIELDS': + return { ...state, fields: action.payload }; + + case 'SET_TAGS': + return { ...state, tags: action.payload }; + + case 'SET_SOIL_TYPES': + return { ...state, soilTypes: action.payload }; + + case 'SET_PLANTING_MODES': + return { ...state, plantingModes: action.payload }; + + case 'UPDATE_FILTER': + return { + ...state, + filters: { + ...state.filters, + [action.payload.key]: action.payload.value, + }, + }; + + case 'TOGGLE_ARRAY_FILTER': + const { key, value } = action.payload; + const currentArray = state.filters[key]; + const newArray = currentArray.includes(value) + ? currentArray.filter(v => v !== value) + : [...currentArray, value]; + + return { + ...state, + filters: { + ...state.filters, + [key]: newArray, + }, + }; + + case 'CLEAR_FILTERS': + return { + ...state, + filters: { + soilTypes: [], + plantingModes: [], + tags: [], + minArea: '', + maxArea: '', + keyword: '', + }, + statistics: null, + }; + + case 'SET_STATISTICS': + return { ...state, statistics: action.payload }; + + case 'SET_CHART_TYPE': + return { ...state, chartType: action.payload }; + + case 'SET_LOADING': + return { ...state, loading: action.payload }; + + default: + return state; + } +} + +// Context 类型定义 +export interface LandStatisticsContextType { + state: LandStatisticsState; + dispatch: React.Dispatch; + loadData: (forceReload?: boolean) => void; + executeQuery: () => void; + exportData: () => void; + handleFilterChange: (key: keyof FilterCondition, value: any) => void; + handleToggleArrayFilter: (key: 'soilTypes' | 'plantingModes' | 'tags', value: string) => void; + handleClearFilters: () => void; + handleChartTypeChange: (type: 'bar' | 'pie') => void; +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/land-information/archive/context/page.tsx b/crop-x/src/app/(app)/land-information/archive/context/page.tsx new file mode 100644 index 0000000..46478a0 --- /dev/null +++ b/crop-x/src/app/(app)/land-information/archive/context/page.tsx @@ -0,0 +1,490 @@ +'use client'; + +import { createContext, useContext, useReducer, useEffect, ReactNode } from 'react'; +import { toast } from 'sonner'; +import { Button } from '@/components/ui/button'; +import { + LandStatisticsReducer, + initialState, + FilterCondition, + StatisticsResult, + LandStatisticsContextType, +} from './components/landStatisticsReducer'; +import { FilterPanel } from './components/FilterPanel'; +import { StatisticsResults } from './components/StatisticsResults'; +import { UsageExamples } from './components/UsageExamples'; + +// Context 创建 +const LandStatisticsContext = createContext(null); + +// Provider 组件 +export function LandStatisticsProvider({ children }: { children: ReactNode }) { + const [state, dispatch] = useReducer(LandStatisticsReducer, initialState); + + const loadData = (forceReload = false) => { + // 加载地块数据 + const fieldsData = localStorage.getItem('land_archive_data'); + if (fieldsData && !forceReload) { + dispatch({ type: 'SET_FIELDS', payload: JSON.parse(fieldsData) }); + } else { + // 初始化测试数据 - 包含所有土壤类型和种植模式 + const testFields = [ + { + id: '1', + code: 'TD001', + name: '东区沙质土试验田', + area: 85.5, + location: '东区1号地块', + soilType: 'sandy', + plantingMode: 'conventional', + tags: ['有机种植', '高产示范', '滴灌设施'], + status: 'active' as const, + description: '东区主要试验地块', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { + id: '2', + code: 'TD002', + name: '南区黏质土种植区', + area: 120.8, + location: '南区2号地块', + soilType: 'clay', + plantingMode: 'organic', + tags: ['有机种植', '生态种植', '智能监测'], + status: 'active' as const, + description: '南区有机种植示范区', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { + id: '3', + code: 'TD003', + name: '西区壤质土生产基地', + area: 95.2, + location: '西区3号地块', + soilType: 'loam', + plantingMode: 'greenhouse', + tags: ['科技示范', '智能监测', '节水灌溉'], + status: 'active' as const, + description: '西区温室生产基地', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { + id: '4', + code: 'TD004', + name: '北区泥炭土水培区', + area: 45.6, + location: '北区4号地块', + soilType: 'peat', + plantingMode: 'hydroponic', + tags: ['水培种植', '智能监测', '绿色种植'], + status: 'active' as const, + description: '北区水培种植示范区', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { + id: '5', + code: 'TD005', + name: '中央区石灰质土种植区', + area: 78.9, + location: '中央区5号地块', + soilType: 'chalky', + plantingMode: 'aeroponic', + tags: ['气培种植', '科技示范', '滴灌设施'], + status: 'active' as const, + description: '中央区气培种植试验田', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { + id: '6', + code: 'TD006', + name: '东区粉质土生态园', + area: 65.3, + location: '东区6号地块', + soilType: 'silty', + plantingMode: 'organic', + tags: ['生态种植', '绿色种植', '节水灌溉'], + status: 'active' as const, + description: '东区生态循环种植园', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { + id: '7', + code: 'TD007', + name: '南区岩石土改良区', + area: 35.7, + location: '南区7号地块', + soilType: 'rocky', + plantingMode: 'conventional', + tags: ['科技示范', '节水灌溉', '高产示范'], + status: 'active' as const, + description: '南区岩石土改良试验田', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { + id: '8', + code: 'TD008', + name: '西区沙质土有机基地', + area: 110.4, + location: '西区8号地块', + soilType: 'sandy', + plantingMode: 'organic', + tags: ['有机种植', '绿色种植', '智能监测'], + status: 'active' as const, + description: '西区有机种植基地', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { + id: '9', + code: 'TD009', + name: '北区黏质土传统区', + area: 88.6, + location: '北区9号地块', + soilType: 'clay', + plantingMode: 'conventional', + tags: ['传统种植', '滴灌设施', '高产示范'], + status: 'active' as const, + description: '北区传统种植示范区', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + { + id: '10', + code: 'TD010', + name: '中央区壤质土智能园', + area: 92.1, + location: '中央区10号地块', + soilType: 'loam', + plantingMode: 'greenhouse', + tags: ['智能监测', '科技示范', '节水灌溉'], + status: 'active' as const, + description: '中央区智能温室园区', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + } + ]; + dispatch({ type: 'SET_FIELDS', payload: testFields }); + localStorage.setItem('land_archive_data', JSON.stringify(testFields)); + } + + // 加载标签数据 + const tagsData = localStorage.getItem('land_archive_custom_tags'); + if (tagsData) { + dispatch({ type: 'SET_TAGS', payload: JSON.parse(tagsData) }); + } else { + // 初始化默认标签 + const defaultTags = [ + { id: '1', name: '有机种植', color: '#22c55e', description: '符合有机种植标准的地块', createdAt: new Date().toISOString() }, + { id: '2', name: '高产示范', color: '#3b82f6', description: '高产示范田', createdAt: new Date().toISOString() }, + { id: '3', name: '滴灌设施', color: '#f97316', description: '配备滴灌系统的地块', createdAt: new Date().toISOString() }, + { id: '4', name: '智能监测', color: '#a855f7', description: '安装了智能监测设备的地块', createdAt: new Date().toISOString() }, + { id: '5', name: '生态种植', color: '#10b981', description: '采用生态循环种植模式', createdAt: new Date().toISOString() }, + { id: '6', name: '科技示范', color: '#ef4444', description: '农业科技示范地块', createdAt: new Date().toISOString() }, + { id: '7', name: '节水灌溉', color: '#06b6d4', description: '采用节水灌溉技术', createdAt: new Date().toISOString() }, + { id: '8', name: '绿色种植', color: '#84cc16', description: '绿色环保种植方式', createdAt: new Date().toISOString() }, + ]; + dispatch({ type: 'SET_TAGS', payload: defaultTags }); + localStorage.setItem('land_archive_custom_tags', JSON.stringify(defaultTags)); + } + + // 加载土壤类型 + const soilTypesData = localStorage.getItem('land_soil_types'); + if (soilTypesData) { + dispatch({ type: 'SET_SOIL_TYPES', payload: JSON.parse(soilTypesData) }); + } else { + // 初始化默认土壤类型 + const defaultSoilTypes = [ + { id: '1', key: 'sandy', name: '沙质土', color: '#f59e0b' }, + { id: '2', key: 'clay', name: '黏质土', color: '#8b5cf6' }, + { id: '3', key: 'loam', name: '壤质土', color: '#22c55e' }, + { id: '4', key: 'peat', name: '泥炭土', color: '#06b6d4' }, + { id: '5', key: 'chalky', name: '石灰质土', color: '#ec4899' }, + { id: '6', key: 'silty', name: '粉质土', color: '#f97316' }, + { id: '7', key: 'rocky', name: '岩石土', color: '#6b7280' } + ]; + dispatch({ type: 'SET_SOIL_TYPES', payload: defaultSoilTypes }); + localStorage.setItem('land_soil_types', JSON.stringify(defaultSoilTypes)); + } + + // 加载种植模式 + const plantingModesData = localStorage.getItem('land_planting_modes'); + if (plantingModesData) { + dispatch({ type: 'SET_PLANTING_MODES', payload: JSON.parse(plantingModesData) }); + } else { + // 初始化默认种植模式 + const defaultPlantingModes = [ + { id: '1', key: 'conventional', name: '传统种植', emoji: '🌾' }, + { id: '2', key: 'organic', name: '有机种植', emoji: '🌱' }, + { id: '3', key: 'greenhouse', name: '温室种植', emoji: '🏠' }, + { id: '4', key: 'hydroponic', name: '水培种植', emoji: '💧' }, + { id: '5', key: 'aeroponic', name: '气培种植', emoji: '☁️' } + ]; + dispatch({ type: 'SET_PLANTING_MODES', payload: defaultPlantingModes }); + localStorage.setItem('land_planting_modes', JSON.stringify(defaultPlantingModes)); + } + }; + + const handleFilterChange = (key: keyof FilterCondition, value: any) => { + dispatch({ type: 'UPDATE_FILTER', payload: { key, value } }); + }; + + const handleToggleArrayFilter = (key: 'soilTypes' | 'plantingModes' | 'tags', value: string) => { + dispatch({ type: 'TOGGLE_ARRAY_FILTER', payload: { key, value } }); + }; + + const handleClearFilters = () => { + dispatch({ type: 'CLEAR_FILTERS' }); + toast.success('筛选条件已清空'); + }; + + const executeQuery = () => { + // 应用筛选条件 + let filteredFields = [...state.fields]; + + // 关键词筛选 + if (state.filters.keyword) { + const keyword = state.filters.keyword.toLowerCase(); + filteredFields = filteredFields.filter(f => + f.name.toLowerCase().includes(keyword) || + f.code.toLowerCase().includes(keyword) || + f.location?.toLowerCase().includes(keyword) + ); + } + + // 土壤类型筛选 + if (state.filters.soilTypes.length > 0) { + filteredFields = filteredFields.filter(f => + state.filters.soilTypes.includes(f.soilType) + ); + } + + // 种植模式筛选 + if (state.filters.plantingModes.length > 0) { + filteredFields = filteredFields.filter(f => + state.filters.plantingModes.includes(f.plantingMode) + ); + } + + // 标签筛选 + if (state.filters.tags.length > 0) { + filteredFields = filteredFields.filter(f => + state.filters.tags.some(tag => f.tags.includes(tag)) + ); + } + + // 面积范围筛选 + if (state.filters.minArea) { + const minArea = parseFloat(state.filters.minArea); + filteredFields = filteredFields.filter(f => f.area >= minArea); + } + if (state.filters.maxArea) { + const maxArea = parseFloat(state.filters.maxArea); + filteredFields = filteredFields.filter(f => f.area <= maxArea); + } + + if (filteredFields.length === 0) { + toast.warning('未找到符合条件的地块'); + dispatch({ type: 'SET_STATISTICS', payload: null }); + return; + } + + // 计算统计结果 + const totalCount = filteredFields.length; + const totalArea = filteredFields.reduce((sum, f) => sum + f.area, 0); + const avgArea = totalArea / totalCount; + const maxArea = Math.max(...filteredFields.map(f => f.area)); + const minArea = Math.min(...filteredFields.map(f => f.area)); + + // 土壤类型分布 - 显示所有定义的土壤类型 + const soilTypeDistribution = state.soilTypes.map(soilType => { + const count = filteredFields.filter(f => f.soilType === soilType.key).length; + const area = filteredFields + .filter(f => f.soilType === soilType.key) + .reduce((sum, f) => sum + f.area, 0); + + return { + name: soilType.name, + count, + area, + color: soilType.color, + }; + }); + + // 种植模式分布 - 显示所有定义的种植模式 + const plantingModeDistribution = state.plantingModes.map(mode => { + const count = filteredFields.filter(f => f.plantingMode === mode.key).length; + const area = filteredFields + .filter(f => f.plantingMode === mode.key) + .reduce((sum, f) => sum + f.area, 0); + + return { + name: mode.name, + count, + area, + emoji: mode.emoji, + }; + }); + + // 标签分布 + const tagMap = new Map(); + filteredFields.forEach(f => { + f.tags.forEach(tag => { + tagMap.set(tag, (tagMap.get(tag) || 0) + 1); + }); + }); + + const tagDistribution = Array.from(tagMap.entries()).map(([name, count]) => { + const tag = state.tags.find(t => t.name === name); + return { + name, + count, + color: tag?.color || '#6b7280', + }; + }).sort((a, b) => b.count - a.count); + + const statisticsResult: StatisticsResult = { + totalCount, + totalArea, + avgArea, + maxArea, + minArea, + soilTypeDistribution, + plantingModeDistribution, + tagDistribution, + }; + + dispatch({ type: 'SET_STATISTICS', payload: statisticsResult }); + toast.success(`查询完成,找到 ${totalCount} 个地块`); + }; + + const handleChartTypeChange = (type: 'bar' | 'pie') => { + dispatch({ type: 'SET_CHART_TYPE', payload: type }); + }; + + const exportData = () => { + if (!state.statistics) { + toast.error('请先执行查询'); + return; + } + + const data = { + 查询时间: new Date().toLocaleString('zh-CN'), + 筛选条件: state.filters, + 统计结果: state.statistics, + }; + + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `地块统计分析_${new Date().getTime()}.json`; + a.click(); + URL.revokeObjectURL(url); + toast.success('数据导出成功'); + }; + + const contextValue: LandStatisticsContextType = { + state, + dispatch, + loadData, + executeQuery, + exportData, + handleFilterChange, + handleToggleArrayFilter, + handleClearFilters, + handleChartTypeChange, + }; + + return ( + + {children} + + ); +} + +// Hook +export function useLandStatistics() { + const context = useContext(LandStatisticsContext); + if (!context) { + throw new Error('useLandStatistics must be used within LandStatisticsProvider'); + } + return context; +} + +export default function LandStatisticsPage() { + return ( + + + + ); +} + +function LandStatistics() { + const { state, loadData, executeQuery, exportData, handleFilterChange, handleToggleArrayFilter, handleClearFilters, handleChartTypeChange } = useLandStatistics(); + + useEffect(() => { + loadData(); + }, []); + + const reloadTestData = () => { + localStorage.removeItem('land_archive_data'); + localStorage.removeItem('land_archive_custom_tags'); + localStorage.removeItem('land_soil_types'); + localStorage.removeItem('land_planting_modes'); + loadData(true); + handleClearFilters(); + toast.success('测试数据已重新加载'); + }; + + return ( +
+
+
+

统计分析

+

+ 灵活的地块筛选和统计查询功能 +

+
+ +
+ + {/* 筛选条件 */} + + + {/* 统计结果 */} + {state.statistics && ( + + )} + + {/* 使用示例 */} + +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/land-information/map/spatial-query/components/ExportDialog.tsx b/crop-x/src/app/(app)/land-information/map/spatial-query/components/ExportDialog.tsx new file mode 100644 index 0000000..291598a --- /dev/null +++ b/crop-x/src/app/(app)/land-information/map/spatial-query/components/ExportDialog.tsx @@ -0,0 +1,316 @@ +'use client'; + +import { useState } from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Label } from '@/components/ui/label'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Textarea } from '@/components/ui/textarea'; +import { Badge } from '@/components/ui/badge'; +import { SpatialField } from './spatialQueryReducer'; +import { + Download, + FileText, + Globe, + Database, + Code, + Copy, + CheckCircle +} from 'lucide-react'; +import { generateGeoJSON, generateKML, generateCSV, generateSQLExample } from './spatialQueryUtils'; +import { toast } from 'sonner'; + +interface ExportDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + results: SpatialField[]; + queryType: string; + queryGeometry: any; + bufferDistance?: number; +} + +export function ExportDialog({ + open, + onOpenChange, + results, + queryType, + queryGeometry, + bufferDistance +}: ExportDialogProps) { + const [exportFormat, setExportFormat] = useState<'geojson' | 'kml' | 'csv'>('geojson'); + const [includeGeometry, setIncludeGeometry] = useState(true); + const [includeAttributes, setIncludeAttributes] = useState(true); + const [previewContent, setPreviewContent] = useState(''); + const [showPreview, setShowPreview] = useState(false); + const [copied, setCopied] = useState(false); + + const generateExportData = () => { + let data = ''; + + switch (exportFormat) { + case 'geojson': + data = generateGeoJSON(results); + break; + case 'kml': + data = generateKML(results); + break; + case 'csv': + data = generateCSV(results); + break; + } + + return data; + }; + + const handlePreview = () => { + const data = generateExportData(); + setPreviewContent(data.substring(0, 1000) + (data.length > 1000 ? '...' : '')); + setShowPreview(true); + }; + + const handleDownload = () => { + const data = generateExportData(); + const blob = new Blob([data], { + type: getContentType(exportFormat) + }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = getFileName(exportFormat); + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + toast.success(`数据已导出为 ${exportFormat.toUpperCase()} 格式`); + onOpenChange(false); + }; + + const handleCopyToClipboard = () => { + const data = generateExportData(); + navigator.clipboard.writeText(data).then(() => { + setCopied(true); + toast.success('数据已复制到剪贴板'); + setTimeout(() => setCopied(false), 2000); + }); + }; + + const getContentType = (format: string): string => { + switch (format) { + case 'geojson': + return 'application/json'; + case 'kml': + return 'application/vnd.google-earth.kml+xml'; + case 'csv': + return 'text/csv'; + default: + return 'text/plain'; + } + }; + + const getFileName = (format: string): string => { + const timestamp = new Date().toISOString().slice(0, 10); + return `spatial_query_results_${timestamp}.${format}`; + }; + + const formatInfo = { + geojson: { + name: 'GeoJSON', + description: '开放的地理空间数据格式,支持几何和属性信息', + icon: Globe, + color: 'text-green-600 dark:text-green-400', + bgColor: 'bg-green-50 dark:bg-green-950' + }, + kml: { + name: 'KML', + description: 'Google Earth 支持的地理标记语言', + icon: FileText, + color: 'text-blue-600 dark:text-blue-400', + bgColor: 'bg-blue-50 dark:bg-blue-950' + }, + csv: { + name: 'CSV', + description: '表格数据格式,适合在Excel等软件中分析', + icon: Database, + color: 'text-purple-600 dark:text-purple-400', + bgColor: 'bg-purple-50 dark:bg-purple-950' + } + }; + + return ( + + + + 导出查询结果 + + 将空间查询结果导出为不同格式的数据文件 + + + +
+ {/* 导出统计 */} +
+
+
+ 地块数量 +
{results.length} 个
+
+
+ 总面积 +
+ {results.reduce((sum, field) => sum + field.area, 0).toFixed(1)} 亩 +
+
+
+ 查询类型 +
{getQueryTypeName(queryType)}
+
+
+ 导出时间 +
{new Date().toLocaleString()}
+
+
+
+ + {/* 格式选择 */} +
+ + setExportFormat(value)}> + {Object.entries(formatInfo).map(([key, info]) => { + const IconComponent = info.icon; + return ( +
+ + +
+ ); + })} +
+
+ + {/* 导出选项 */} +
+ +
+
+ setIncludeGeometry(checked as boolean)} + /> + +
+
+ setIncludeAttributes(checked as boolean)} + /> + +
+
+
+ + {/* 预览区域 */} + {showPreview && ( +
+
+ + + 显示前1000字符 + +
+