子仓库提交

This commit is contained in:
2025-11-10 09:19:56 +08:00
parent 62f92213f7
commit 5feb24e4e2
733 changed files with 141413 additions and 0 deletions

View File

@@ -0,0 +1,299 @@
'use client';
import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Slider } from '@/components/ui/slider';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Sliders,
RotateCcw,
Info,
Scale
} from 'lucide-react';
import { SpatialAnalysisState, SpatialAnalysisAction } from './spatialAnalysisReducer';
interface WeightConfigurationProps {
state: SpatialAnalysisState;
dispatch: React.Dispatch<SpatialAnalysisAction>;
isDialog?: boolean;
}
export default function WeightConfiguration({ state, dispatch, isDialog = false }: WeightConfigurationProps) {
const categories = [
{ id: 'soil', name: '土壤条件', icon: '🌱', color: 'blue' },
{ id: 'climate', name: '气候条件', icon: '🌤️', color: 'green' },
{ id: 'topography', name: '地形条件', icon: '⛰️', color: 'orange' },
{ id: 'infrastructure', name: '基础设施', icon: '🏗️', color: 'purple' }
];
const handleUpdateWeight = (category: string, value: number) => {
const newWeightConfig = {
...state.weightConfig,
[category]: value / 100
};
dispatch({ type: 'SET_WEIGHT_CONFIG', payload: newWeightConfig });
};
const handleResetToDefaults = () => {
const defaultConfig = {
soil: 0.35,
climate: 0.30,
topography: 0.20,
infrastructure: 0.15
};
dispatch({ type: 'SET_WEIGHT_CONFIG', payload: defaultConfig });
};
const handleBalanceWeights = () => {
// 自动平衡权重确保总和为100%
const total = Object.values(state.weightConfig).reduce((sum, val) => sum + val, 0);
if (total > 0) {
const balancedConfig = Object.fromEntries(
Object.entries(state.weightConfig).map(([key, value]) => [key, value / total])
) as typeof state.weightConfig;
dispatch({ type: 'SET_WEIGHT_CONFIG', payload: balancedConfig });
}
};
const getTotalWeight = () => {
return Object.values(state.weightConfig).reduce((sum, val) => sum + val, 0);
};
const categoryConfigs = categories.map(category => ({
...category,
weight: state.weightConfig[category.id as keyof typeof state.weightConfig],
percentage: Math.round(state.weightConfig[category.id as keyof typeof state.weightConfig] * 100)
}));
const content = (
<div className="space-y-6">
{/* 权重总览 */}
<Card className="p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-medium flex items-center gap-2">
<Scale className="w-5 h-5" />
</h3>
<div className="flex items-center gap-3">
<div className="text-right">
<div className={`text-2xl font-bold ${
Math.abs(getTotalWeight() - 1) < 0.01 ? 'text-green-600' : 'text-red-600'
}`}>
{Math.round(getTotalWeight() * 100)}%
</div>
<div className="text-xs text-muted-foreground"></div>
</div>
<Button variant="outline" size="sm" onClick={handleResetToDefaults}>
<RotateCcw className="w-4 h-4 mr-2" />
</Button>
<Button variant="outline" size="sm" onClick={handleBalanceWeights}>
<Sliders className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
{/* 权重可视化 */}
<div className="space-y-4">
{categoryConfigs.map(category => (
<div key={category.id} className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-lg">{category.icon}</span>
<span className="font-medium">{category.name}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-lg font-bold">{category.percentage}%</span>
<Badge
variant="outline"
className={
category.id === 'soil' ? 'border-blue-200 text-blue-600' :
category.id === 'climate' ? 'border-green-200 text-green-600' :
category.id === 'topography' ? 'border-orange-200 text-orange-600' :
'border-purple-200 text-purple-600'
}
>
{category.weight.toFixed(2)}
</Badge>
</div>
</div>
<Progress
value={category.weight * 100}
className={`h-3 ${
category.id === 'soil' ? 'bg-blue-100' :
category.id === 'climate' ? 'bg-green-100' :
category.id === 'topography' ? 'bg-orange-100' :
'bg-purple-100'
}`}
/>
</div>
))}
{Math.abs(getTotalWeight() - 1) > 0.01 && (
<div className="pt-2 border-t">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground"></span>
<span className="text-red-600 font-medium">
{Math.abs((getTotalWeight() - 1) * 100).toFixed(1)}%
</span>
</div>
<Progress value={getTotalWeight() * 100} className="h-2 mt-1" />
<p className="text-xs text-red-600 mt-1">
100%"自动平衡"
</p>
</div>
)}
</div>
</Card>
{/* 权重调节面板 */}
<Card className="p-6">
<h3 className="text-lg font-medium mb-6"></h3>
<div className="space-y-6">
{categoryConfigs.map(category => (
<div key={category.id} className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-xl">{category.icon}</span>
<div>
<h4 className="font-medium">{category.name}</h4>
<p className="text-sm text-muted-foreground">
{category.id === 'soil' && '包括土壤pH、有机质、养分含量等指标'}
{category.id === 'climate' && '包括温度、降水、光照等气象条件'}
{category.id === 'topography' && '包括海拔、坡度等地形特征'}
{category.id === 'infrastructure' && '包括灌溉、交通等设施条件'}
</p>
</div>
</div>
<div className="text-right min-w-[80px]">
<div className="text-2xl font-bold">{category.percentage}%</div>
<div className="text-xs text-muted-foreground"></div>
</div>
</div>
<div className="space-y-2">
<Slider
value={[category.weight * 100]}
onValueChange={([value]) => handleUpdateWeight(category.id, value)}
max={60}
min={5}
step={1}
className="w-full"
/>
<div className="flex justify-between text-xs text-muted-foreground">
<span>5%</span>
<span>60%</span>
</div>
</div>
</div>
))}
</div>
</Card>
{/* 权重配置说明 */}
<Card className="p-4 bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800">
<div className="flex gap-2">
<Info className="w-5 h-5 text-blue-600 flex-shrink-0" />
<div className="text-sm text-blue-800 dark:text-blue-200">
<p className="font-medium mb-2"></p>
<ul className="space-y-1 text-xs">
<li> <strong> (35%)</strong>: </li>
<li> <strong> (30%)</strong>: </li>
<li> <strong> (20%)</strong>: </li>
<li> <strong> (15%)</strong>: </li>
<li> 100%</li>
<li> </li>
</ul>
</div>
</div>
</Card>
{/* 预设配置 */}
<Card className="p-4">
<h3 className="font-medium mb-4"></h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<Button
variant="outline"
onClick={() => dispatch({
type: 'SET_WEIGHT_CONFIG',
payload: { soil: 0.4, climate: 0.3, topography: 0.2, infrastructure: 0.1 }
})}
>
<div className="text-left">
<div className="font-medium text-sm"></div>
<div className="text-xs text-muted-foreground">40% · 30%</div>
</div>
</Button>
<Button
variant="outline"
onClick={() => dispatch({
type: 'SET_WEIGHT_CONFIG',
payload: { soil: 0.3, climate: 0.4, topography: 0.2, infrastructure: 0.1 }
})}
>
<div className="text-left">
<div className="font-medium text-sm"></div>
<div className="text-xs text-muted-foreground">40% · 30%</div>
</div>
</Button>
<Button
variant="outline"
onClick={() => dispatch({
type: 'SET_WEIGHT_CONFIG',
payload: { soil: 0.3, climate: 0.3, topography: 0.3, infrastructure: 0.1 }
})}
>
<div className="text-left">
<div className="font-medium text-sm"></div>
<div className="text-xs text-muted-foreground">30%</div>
</div>
</Button>
</div>
</Card>
</div>
);
if (isDialog) {
return (
<Dialog open={state.showWeightDialog} onOpenChange={() => dispatch({ type: 'TOGGLE_WEIGHT_DIALOG' })}>
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Sliders className="w-5 h-5" />
</DialogTitle>
</DialogHeader>
{content}
</DialogContent>
</Dialog>
);
}
return (
<div>
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-medium"></h3>
<div className="flex items-center gap-2">
<Badge variant={Math.abs(getTotalWeight() - 1) < 0.01 ? 'default' : 'destructive'}>
: {Math.round(getTotalWeight() * 100)}%
</Badge>
</div>
</div>
{content}
</div>
);
}