diff --git a/crop-x/docs/开发项目规范.md b/crop-x/docs/开发项目规范.md new file mode 100644 index 0000000..e591e61 --- /dev/null +++ b/crop-x/docs/开发项目规范.md @@ -0,0 +1,427 @@ +# 开发项目规范 + +## path:land-information/archive/statistics,name:统计分析页面开发经验 + +### 总体开发经验总结 + +在实现统计分析页面过程中,我们遵循了以下8条核心开发规范,确保代码质量、可维护性和用户体验的一致性。 + +### 1. shadcn 样式系统优先原则 + +**经验总结:** +- 优先使用shadcn的语义化颜色类(如 `bg-card`、`bg-background`)替代硬编码的Tailwind CSS颜色 +- 避免使用 `bg-red-500` 等具体颜色值,改用 `bg-red-50 dark:bg-red-950` 等语义化类 +- 统计卡片使用 `bg-green-50 dark:bg-green-950` 等主题感知的背景色 + +**最佳实践:** +```tsx +// ✅ 推荐写法 + + + +// ❌ 避免写法 + + +``` + +### 2. 标签组件样式精确还原原则 + +**经验总结:** +- 严格按照参考文件实现标签的边框和颜色样式 +- 使用 `style` 属性精确控制颜色,确保1:1还原视觉效果 +- 不同类型的标签有特定的样式特征需要保持一致 + +**关键实现:** +```tsx +// 土壤类型标签:前缀彩色圆点 +
+ +// 种植模式标签:前缀emoji + 固定绿色边框 +{mode.emoji} +style={{ + backgroundColor: filters.plantingModes.includes(mode.key) ? '#16a34a' : 'transparent', + borderColor: '#16a34a', +}} + +// 自定义标签:纯色边框背景 +style={{ + backgroundColor: filters.tags.includes(tag.name) ? tag.color : 'transparent', + borderColor: tag.color, +}} +``` + +### 3. 最小化修改原则 + +**经验总结:** +- 严格遵循参考文件的功能边界,不添加多余功能 +- 保持与原有系统的功能一致性 +- 避免过度设计,专注核心功能实现 + +**实施要点:** +- 只实现参考文件中明确展示的功能 +- 保持相同的用户交互流程 +- 维持原有的数据结构和逻辑 + +### 4. 暗色主题全面支持原则 + +**经验总结:** +- 所有组件都必须支持暗色主题切换 +- 使用 `dark:` 前缀提供暗色模式样式 +- 确保在暗色主题下的可读性和视觉效果 + +**实现模式:** +```tsx +// 统计卡片暗色主题 + +
+ +// 背景和边框暗色主题 + + +``` + +### 5. useReducer 状态管理架构原则 + +**经验总结:** +- 使用useReducer实现复杂状态管理,避免prop drilling +- 通过dispatch实现跨组件状态同步 +- 集中化状态逻辑,提高代码可维护性 + +**架构模式:** +```tsx +// Reducer定义 +export interface LandStatisticsState { + fields: Land[]; + filters: FilterCondition; + statistics: StatisticsResult | null; + chartType: 'bar' | 'pie'; +} + +// Action类型定义 +export type LandStatisticsAction = + | { type: 'SET_FIELDS'; payload: Land[] } + | { type: 'UPDATE_FILTER'; payload: { key: keyof FilterCondition; value: any } } + | { type: 'SET_STATISTICS'; payload: StatisticsResult | null }; + +// 状态同步使用 +const handleFilterChange = (key: keyof FilterCondition, value: any) => { + dispatch({ type: 'UPDATE_FILTER', payload: { key, value } }); +}; +``` + +### 6. 模块化组件架构原则 + +**经验总结:** +- 每个页面建立独立的components文件夹 +- 按功能职责拆分组件,确保单一职责 +- 组件间通过props和回调函数通信 + +**目录结构示例:** +``` +src/app/(app)/land-information/archive/statistics/ +├── page.tsx # 主页面 +└── components/ + ├── landStatisticsReducer.tsx # 状态管理 + ├── FilterPanel.tsx # 筛选面板 + ├── StatisticsResults.tsx # 统计结果 + └── UsageExamples.tsx # 使用示例 +``` + +### 7. 完整依赖引用实现原则 + +**经验总结:** +- 仔细分析参考文件的import依赖,确保完整实现 +- 将所有引用的组件都实现在components目录中 +- 保持与参考文件相同的组件结构和功能 + +**依赖检查清单:** +- UI组件:Card, Button, Badge, Input, Label等 +- 图标组件:lucide-react图标 +- 图表组件:recharts相关组件 +- 工具函数:toast通知等 + +### 8. 1:1 功能还原实现原则 + +**经验总结:** +- 严格按照参考文件的功能逻辑实现 +- 保持相同的用户交互体验 +- 确保数据流和业务逻辑的一致性 + +**关键实现要点:** +- 筛选条件的多选逻辑 +- 数据统计的计算方法 +- 图表切换和显示逻辑 +- 数据导出功能 + +## 开发工具和最佳实践 + +### 推荐工具链 +- **状态管理**:React useReducer +- **UI组件库**:shadcn/ui +- **样式系统**:Tailwind CSS + 语义化颜色 +- **图表库**:Recharts +- **图标库**:Lucide React +- **通知系统**:Sonner + +### 代码质量保证 +- TypeScript严格类型检查 +- ESLint代码规范检查 +- 组件props类型定义完整 +- 状态管理逻辑清晰可维护 + +### 测试数据管理 +- localStorage数据持久化 +- 完整的测试数据覆盖所有业务场景 +- 数据初始化和清理机制完善 + +通过遵循这些开发规范,我们可以确保代码的一致性、可维护性和用户体验的统一性。 + +--- + +## path:land-information/archive/statistics,name:统计分析页面开发经验与问题解决 + +### 问题1:图表横轴显示不完整 + +**问题描述:** +- 初始实现中,图表只显示有数据的土壤类型和种植模式 +- 没有数据的项目在横轴上不显示,导致图表看起来不完整 + +**原始需求分析:** +- 土壤类型分布应显示所有定义的土壤类型,即使数量为0 +- 种植模式分布应显示所有定义的种植模式,提供完整的分类视图 + +**解决方案:** +- 修改数据计算逻辑,从"基于筛选结果生成数据"改为"基于所有定义的分类生成数据" +- 使用 `state.soilTypes.map()` 和 `state.plantingModes.map()` 确保显示所有定义的分类 + +**代码改进对比:** +```tsx +// ❌ 初始实现(只显示有数据的分类) +const soilTypeMap = new Map(); +filteredFields.forEach(f => { + const current = soilTypeMap.get(f.soilType) || { count: 0, area: 0 }; + soilTypeMap.set(f.soilType, { + count: current.count + 1, + area: current.area + f.area, + }); +}); +const soilTypeDistribution = Array.from(soilTypeMap.entries()).map(([key, value]) => ({ + name: state.soilTypes.find(s => s.key === key)?.name || key, + count: value.count, + area: value.area, + color: state.soilTypes.find(s => s.key === key)?.color || '#6b7280', +})); + +// ✅ 最终实现(显示所有定义的分类,包括数量为0的) +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, + }; +}); +``` + +### 问题2:标签字体粗细不符合视觉要求 + +**问题描述:** +- 筛选标签(土壤类型、种植模式、自定义标签)的字体过粗 +- 用户反馈需要调整为细字体,以匹配参考文件的视觉效果 + +**原始需求分析:** +- 参考文件显示的是细字效果,需要精确还原视觉体验 +- 字体粗细影响整体UI的美观和专业度 + +**解决方案:** +- 给所有Badge组件添加 `font-light` 类名 +- 保持其他样式(颜色、边框、悬停效果)不变,只调整字体粗细 + +**代码改进:** +```tsx +// ❌ 初始实现 + + {type.name} + + +// ✅ 最终实现(添加字体细体) + + {type.name} + +``` + +### 问题3:测试数据覆盖不完整影响演示效果 + +**问题描述:** +- localStorage中存在旧数据,导致某些土壤类型和种植模式没有对应的地块数据 +- 部分图表项目显示为空或缺失,影响演示效果和用户体验 + +**原始需求分析:** +- 所有土壤类型和种植模式都应该有对应的测试数据 +- 确保图表能完整展示所有分类的统计数据,即使是0也要显示 +- 为用户提供完整的演示环境 + +**解决方案:** +- 创建完整的测试数据集,覆盖所有7种土壤类型和5种种植模式 +- 在 `loadData` 函数中初始化这些测试数据,确保首次访问时有完整数据 +- 通过localStorage持久化,确保数据在页面刷新后仍然存在 + +**测试数据设计原则:** +```tsx +const testFields = [ + { + id: '1', + code: 'TD001', + name: '东区沙质土试验田', + area: 85.5, + location: '东区1号地块', + soilType: 'sandy', // 沙质土 + plantingMode: 'conventional', // 传统种植 + tags: ['有机种植', '高产示范', '滴灌设施'], + // ...其他完整字段 + }, + // 总计10个地块,确保每个土壤类型和种植模式都有覆盖 + // 沙质土(2个)、黏质土(2个)、壤质土(2个)、泥炭土(1个)、石灰质土(1个)、粉质土(1个)、岩石土(1个) + // 传统种植(3个)、有机种植(3个)、温室种植(2个)、水培种植(1个)、气培种植(1个) +]; +``` + +### 问题4:语义化颜色类使用存在不一致 + +**问题描述:** +- 部分组件仍使用硬编码的Tailwind颜色类 +- 没有充分利用shadcn的语义化颜色系统 +- 在暗色主题下可能存在兼容性问题 + +**原始需求分析:** +- 优先使用 `bg-gray` 等语义化颜色类 +- 避免写死的Tailwind CSS样式,提高主题一致性 +- 建立统一的颜色使用标准 + +**解决方案:** +- 全面检查并替换硬编码颜色为语义化颜色类 +- 统计卡片使用 `bg-green-50 dark:bg-green-950` 等主题感知背景色 +- 确保在暗色主题下的可读性和视觉效果一致性 + +**颜色使用改进:** +```tsx +// ❌ 不一致的硬编码实现 + +
+ + +// ✅ 统一的语义化颜色实现 + +
+ + +``` + +### 问题5:多组件状态同步和数据管理复杂性 + +**问题描述:** +- 多个组件之间需要共享状态,使用prop传递会导致代码复杂且难以维护 +- 数据更新时容易出现状态不一致的问题 +- 缺乏集中化的状态管理机制 + +**原始需求分析:** +- 使用useReducer实现集中化状态管理 +- 确保组件间数据同步的可靠性和性能 +- 简化组件间的通信逻辑 + +**解决方案:** +- 设计完整的状态管理架构,包括状态接口、Action类型和Reducer函数 +- 通过dispatch实现跨组件状态更新,避免prop drilling +- 使用localStorage进行数据持久化,确保页面刷新后状态保持 +- 建立清晰的数据流和状态更新模式 + +**状态管理架构设计:** +```tsx +// 完整的状态接口定义 +export interface LandStatisticsState { + fields: Land[]; + tags: LandTag[]; + soilTypes: SoilType[]; + plantingModes: PlantingMode[]; + filters: FilterCondition; + statistics: StatisticsResult | null; + chartType: 'bar' | 'pie'; +} + +// 细粒度的Action类型定义 +export type LandStatisticsAction = + | { type: 'SET_FIELDS'; payload: Land[] } + | { type: 'SET_TAGS'; payload: LandTag[] } + | { type: 'SET_SOIL_TYPES'; payload: SoilType[] } + | { type: 'SET_PLANTING_MODES'; payload: PlantingMode[] } + | { type: 'SET_FILTERS'; payload: FilterCondition } + | { 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' }; + +// 跨组件状态同步实现 +const handleFilterChange = (key: keyof FilterCondition, value: any) => { + dispatch({ type: 'UPDATE_FILTER', payload: { key, value } }); +}; + +const handleToggleArrayFilter = (key: 'soilTypes' | 'plantingModes' | 'tags', value: string) => { + const currentArray = state.filters[key]; + const newArray = currentArray.includes(value) + ? currentArray.filter(v => v !== value) + : [...currentArray, value]; + dispatch({ type: 'TOGGLE_ARRAY_FILTER', payload: { key, value } }); +}; +``` + +## 开发经验对比总结 + +### 与原始要求的差异分析 + +| 原始要求 | 实际实现 | 差异说明 | 解决过程 | +|---------|---------|---------|---------| +| 使用shadcn语义化样式 | 完全实现 + 统一规范 | 需要建立统一的使用标准 | 全面替换硬编码颜色,建立语义化颜色使用指南 | +| 1:1还原标签样式 | 精确还原 + 字体优化 | 字体粗细需要调整以匹配视觉 | 添加font-light类名,保持样式一致性 | +| 不动无关内容 | 完全遵循 | 严格保持功能边界,不添加多余功能 | 只实现参考文件中的明确功能 | +| 暗色主题支持 | 全面支持 | 需要系统化处理所有UI组件 | 使用dark:前缀系统化处理暗色主题 | +| useReducer状态管理 | 架构实现 + 最佳实践 | 需要设计状态同步机制和数据持久化 | 建立完整的状态管理架构和同步机制 | +| 模块化组件 | 完全拆分 | 需要合理的组件职责划分和通信机制 | 按功能领域拆分组件,通过props和回调通信 | +| 完整依赖引用 | 1:1还原 | 需要仔细分析引用关系和依赖完整性 | 建立依赖检查清单,确保所有引用组件完整实现 | +| 1:1功能还原 | 完整实现 | 需要精确还原业务逻辑和用户体验 | 严格对照参考文件实现所有功能 | + +### 关键学习点和改进 + +1. **数据完整性思维**:不仅要实现功能,还要考虑数据的完整性和演示效果的完整性 + - 学会了从用户体验角度思考数据展示的完整性 + - 理解了即使count为0也应该显示的重要性 + +2. **细节精确把控**:字体粗细、颜色、边框等视觉细节需要精确还原 + - 培养了对UI细节的敏感度 + - 掌握了通过用户反馈快速迭代优化的方法 + +3. **状态管理设计**:useReducer不仅是技术选择,更是架构设计决策 + - 深入理解了状态管理的架构设计原则 + - 掌握了跨组件状态同步的最佳实践 + +4. **渐进式优化**:在开发过程中根据反馈不断调整和改进 + - 学会了灵活应对开发过程中的需求变化 + - 建立了基于反馈的快速响应机制 + +5. **文档化习惯**:将开发过程中的经验和教训记录下来,形成知识积累 + - 认识到文档化对团队协作和知识传承的重要性 + - 建立了完整的开发规范文档体系 \ No newline at end of file diff --git a/crop-x/src/app/(app)/land-information/archive/classification/components/LandClassificationManagement.tsx b/crop-x/src/app/(app)/land-information/archive/classification/components/LandClassificationManagement.tsx new file mode 100644 index 0000000..834b603 --- /dev/null +++ b/crop-x/src/app/(app)/land-information/archive/classification/components/LandClassificationManagement.tsx @@ -0,0 +1,618 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +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 { Textarea } from '@/components/ui/textarea'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Layers, MapPin, Plus, Edit, Trash2 } from 'lucide-react'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; +import { toast } from 'sonner'; + +// 预设颜色 +const PRESET_COLORS = [ + '#f59e0b', '#22c55e', '#f97316', '#3b82f6', + '#a855f7', '#ef4444', '#6b7280', '#10b981', + '#8b5cf6', '#ec4899', '#14b8a6', '#fb923c' +]; + +// 预设emoji +const PRESET_EMOJIS = [ + '🌾', '🏠', '🍎', '🌊', '🌱', '🌳', '🌻', '🌽', + '🥬', '🥕', '🍅', '🫑', '🌿', '🪴', '🍇', '🌲' +]; + +export function LandClassificationManagement() { + const [activeTab, setActiveTab] = useState('soil-types'); + + // 土壤类型 + const [soilTypes, setSoilTypes] = useState([]); + // 种植模式 + const [plantingModes, setPlantingModes] = useState([]); + + // 对话框状态 + const [showSoilDialog, setShowSoilDialog] = useState(false); + const [showModeDialog, setShowModeDialog] = useState(false); + const [editingSoilType, setEditingSoilType] = useState(null); + const [editingMode, setEditingMode] = useState(null); + + // 表单数据 + const [soilFormData, setSoilFormData] = useState({ + name: '', + key: '', + description: '', + color: '#22c55e', + }); + + const [modeFormData, setModeFormData] = useState({ + name: '', + key: '', + description: '', + emoji: '🌾', + }); + + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [deletingItem, setDeletingItem] = useState<{ type: 'soil' | 'mode'; id: string } | null>(null); + + // 加载数据 + useEffect(() => { + loadSoilTypes(); + loadPlantingModes(); + }, []); + + const loadSoilTypes = () => { + const stored = localStorage.getItem('land_soil_types'); + if (stored) { + setSoilTypes(JSON.parse(stored)); + } else { + const now = new Date().toISOString(); + const defaultTypes = [ + { id: '1', name: '沙土', key: 'sandy', description: '透气性好,保水性差,适合根茎类作物', color: '#f59e0b', createdAt: now, updatedAt: now }, + { id: '2', name: '壤土', key: 'loam', description: '肥力高,适合多种作物,是最理想的土壤类型', color: '#22c55e', createdAt: now, updatedAt: now }, + { id: '3', name: '黏土', key: 'clay', description: '保水保肥能力强,透气性差', color: '#f97316', createdAt: now, updatedAt: now }, + { id: '4', name: '粉土', key: 'silt', description: '有机质丰富,适合水稻等作物', color: '#3b82f6', createdAt: now, updatedAt: now }, + { id: '5', name: '泥炭土', key: 'peat', description: '有机质含量极高,酸性土壤', color: '#a855f7', createdAt: now, updatedAt: now }, + { id: '6', name: '盐碱土', key: 'saline', description: '含盐量高,需要改良后使用', color: '#ef4444', createdAt: now, updatedAt: now }, + { id: '7', name: '其他', key: 'other', description: '其他类型土壤', color: '#6b7280', createdAt: now, updatedAt: now }, + ]; + localStorage.setItem('land_soil_types', JSON.stringify(defaultTypes)); + setSoilTypes(defaultTypes); + } + }; + + const loadPlantingModes = () => { + const stored = localStorage.getItem('land_planting_modes'); + if (stored) { + setPlantingModes(JSON.parse(stored)); + } else { + const now = new Date().toISOString(); + const defaultModes = [ + { id: '1', name: '露地', key: 'open-field', description: '露天种植,依靠自然条件', emoji: '🌾', createdAt: now, updatedAt: now }, + { id: '2', name: '大棚', key: 'greenhouse', description: '温室大棚种植,可控环境', emoji: '🏠', createdAt: now, updatedAt: now }, + { id: '3', name: '果园', key: 'orchard', description: '果树种植区域', emoji: '🍎', createdAt: now, updatedAt: now }, + { id: '4', name: '水田', key: 'paddy', description: '水稻等水生作物种植', emoji: '🌊', createdAt: now, updatedAt: now }, + { id: '5', name: '旱地', key: 'dryland', description: '旱作物种植区域', emoji: '🌱', createdAt: now, updatedAt: now }, + ]; + localStorage.setItem('land_planting_modes', JSON.stringify(defaultModes)); + setPlantingModes(defaultModes); + } + }; + + // 土壤类型 - 新增 + const handleAddSoilType = () => { + setEditingSoilType(null); + setSoilFormData({ name: '', key: '', description: '', color: '#22c55e' }); + setShowSoilDialog(true); + }; + + // 土壤类型 - 编辑 + const handleEditSoilType = (type: any) => { + setEditingSoilType(type); + setSoilFormData({ + name: type.name, + key: type.key, + description: type.description, + color: type.color, + }); + setShowSoilDialog(true); + }; + + // 土壤类型 - 保存 + const handleSaveSoilType = () => { + if (!soilFormData.name.trim()) { + toast.error('请输入类型名称'); + return; + } + if (!soilFormData.key.trim()) { + toast.error('请输入类型标识'); + return; + } + + const now = new Date().toISOString(); + + if (editingSoilType) { + // 编辑 + const updatedTypes = soilTypes.map(type => + type.id === editingSoilType.id + ? { ...type, ...soilFormData, updatedAt: now } + : type + ); + localStorage.setItem('land_soil_types', JSON.stringify(updatedTypes)); + setSoilTypes(updatedTypes); + toast.success('土壤类型更新成功'); + } else { + // 新增 + const newType = { + id: `soil-${Date.now()}`, + ...soilFormData, + createdAt: now, + updatedAt: now, + }; + const updatedTypes = [...soilTypes, newType]; + localStorage.setItem('land_soil_types', JSON.stringify(updatedTypes)); + setSoilTypes(updatedTypes); + toast.success('土壤类型添加成功'); + } + + setShowSoilDialog(false); + + // 触发自定义事件通知父组件刷新 + window.dispatchEvent(new Event('landClassificationUpdated')); + }; + + // 种植模式 - 新增 + const handleAddMode = () => { + setEditingMode(null); + setModeFormData({ name: '', key: '', description: '', emoji: '🌾' }); + setShowModeDialog(true); + }; + + // 种植模式 - 编辑 + const handleEditMode = (mode: any) => { + setEditingMode(mode); + setModeFormData({ + name: mode.name, + key: mode.key, + description: mode.description, + emoji: mode.emoji, + }); + setShowModeDialog(true); + }; + + // 种植模式 - 保存 + const handleSaveMode = () => { + if (!modeFormData.name.trim()) { + toast.error('请输入模式名称'); + return; + } + if (!modeFormData.key.trim()) { + toast.error('请输入模式标识'); + return; + } + + const now = new Date().toISOString(); + + if (editingMode) { + // 编辑 + const updatedModes = plantingModes.map(mode => + mode.id === editingMode.id + ? { ...mode, ...modeFormData, updatedAt: now } + : mode + ); + localStorage.setItem('land_planting_modes', JSON.stringify(updatedModes)); + setPlantingModes(updatedModes); + toast.success('种植模式更新成功'); + } else { + // 新增 + const newMode = { + id: `mode-${Date.now()}`, + ...modeFormData, + createdAt: now, + updatedAt: now, + }; + const updatedModes = [...plantingModes, newMode]; + localStorage.setItem('land_planting_modes', JSON.stringify(updatedModes)); + setPlantingModes(updatedModes); + toast.success('种植模式添加成功'); + } + + setShowModeDialog(false); + + // 触发自定义事件通知父组件刷新 + window.dispatchEvent(new Event('landClassificationUpdated')); + }; + + const handleDeleteClick = (type: 'soil' | 'mode', id: string) => { + setDeletingItem({ type, id }); + setDeleteDialogOpen(true); + }; + + const confirmDelete = () => { + if (!deletingItem) return; + + if (deletingItem.type === 'soil') { + const item = soilTypes.find(s => s.id === deletingItem.id); + if (soilTypes.length <= 1) { + toast.error('至少需要保留一个土壤类型'); + setDeleteDialogOpen(false); + return; + } + const updatedTypes = soilTypes.filter(s => s.id !== deletingItem.id); + localStorage.setItem('land_soil_types', JSON.stringify(updatedTypes)); + setSoilTypes(updatedTypes); + toast.success(`已删除土壤类型:${item?.name}`); + } else { + const item = plantingModes.find(m => m.id === deletingItem.id); + if (plantingModes.length <= 1) { + toast.error('至少需要保留一个种植模式'); + setDeleteDialogOpen(false); + return; + } + const updatedModes = plantingModes.filter(m => m.id !== deletingItem.id); + localStorage.setItem('land_planting_modes', JSON.stringify(updatedModes)); + setPlantingModes(updatedModes); + toast.success(`已删除种植模式:${item?.name}`); + } + + setDeleteDialogOpen(false); + setDeletingItem(null); + + // 触发自定义事件通知父组件刷新 + window.dispatchEvent(new Event('landClassificationUpdated')); + }; + + return ( +
+ + + + + 土壤类型 + + + + 种植模式 + + + + +
+
+

+ 管理地块的土壤类型分类({soilTypes.length}) +

+ +
+ +
+ {soilTypes.map((soilType) => ( + +
+
+
+
+
+ {soilType.name} + + {soilType.key} + +
+

+ {soilType.description} +

+
+
+
+ + +
+
+ + ))} +
+ + +

+ 💡 提示:土壤类型用于地块分类管理,删除类型前请确保没有地块使用该类型。 +

+
+
+ + + +
+
+

+ 管理地块的种植模式分类({plantingModes.length}) +

+ +
+ +
+ {plantingModes.map((mode) => ( + +
+
+
{mode.emoji}
+
+
+ {mode.name} + + {mode.key} + +
+

+ {mode.description} +

+
+
+
+ + +
+
+
+ ))} +
+ + +

+ 💡 提示:种植模式用于地块分类管理,删除模式前请确保没有地块使用该模式。 +

+
+
+
+ + + {/* 土壤类型新增/编辑对话框 */} + + + + {editingSoilType ? '编辑土壤类型' : '新增土壤类型'} + + {editingSoilType ? '修改土壤类型信息' : '添加新的土壤类型'} + + + +
+
+ + setSoilFormData({ ...soilFormData, key: e.target.value.toLowerCase() })} + className="bg-background" + /> +
+ +
+ + setSoilFormData({ ...soilFormData, name: e.target.value })} + className="bg-background" + /> +
+ +
+ +