# 开发项目规范 ## 通用开发规约 ### 1. 文件头部注释规范(filekorolheader) **规范要求:** 所有页面文件(page.tsx)必须在最上方添加filekorolheader注释,说明文件对应的页面功能、路径和用途。 **格式标准:** ```tsx /** * filekorolheader: [页面名称] - [功能描述] * 功能:[主要功能列表] * 路径:[页面路由路径] * 规范:[遵循的特殊规范说明] */ ``` **示例:** ```tsx /** * filekorolheader: 物联设备数据接入页面 - IoT设备数据管理中心 * 功能:设备列表管理、实时数据监控、数据对比分析、报告生成 * 路径:/ai-crop-model/data-sense-center/iot * 规范:遵循crop-x-new/docs/开发项目规范.md,使用useReducer状态管理,shadcn语义化样式 */ ``` **实施要点:** - 必须放在文件最顶部,在'use client'之前 - 页面名称要准确反映业务功能 - 功能描述要简明扼要,列出核心功能 - 路径必须是完整的路由路径 - 如有特殊规范遵循,需要在规范字段说明 --- ## 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数据持久化 - 完整的测试数据覆盖所有业务场景 - 数据初始化和清理机制完善 通过遵循这些开发规范,我们可以确保代码的一致性、可维护性和用户体验的统一性。 --- ### 9.注意乱码原则 生成的代码注意看看有没有乱码,必须遵守utf-8编码 ### 10.接口调用原则。 接口必须调用 D:\code\repotest\smart-crop-ui\crop-x-new\src\lib\api\sdk.gen.ts 这个里面的接口 比如 /api/v1/departments/tree 这个路径,就是要调用export const getDepartmentTreeApiV1DepartmentsDepartmentsTreeGet = (options?: Options) => { return (options?.client ?? client).get({ security: [ { scheme: 'bearer', type: 'http' } ], url: '/api/v1/departments/departments/tree', ...options }); }; 实际使用的时候,要参考D:\code\repotest\smart-crop-ui\crop-x-new\src\app\(app)\central-config\tenant\audit-history\components\auditHistoryApi.ts 里面 import { getTenantAuditLogsApiV1TenantsAuditLogsGet, } from "@/lib/api/sdk.gen"; 这个引入和用法。 ### 11.Next.js 文件命名规范原则 **规范要求:** 所有文件名必须严格遵循 Next.js 的文件命名规范,确保路由系统和页面组件能够正确识别。 **规范标准:** #### 页面文件命名规范 - **页面文件**:必须使用 `page.tsx` 作为页面文件名 - **布局文件**:必须使用 `layout.tsx` 作为布局文件名 - **加载状态**:必须使用 `loading.tsx` 作为加载状态文件名 - **错误处理**:必须使用 `error.tsx` 作为错误处理文件名 - **未找到页面**:必须使用 `not-found.tsx` 作为404页面文件名 #### 路由参数文件命名规范 - **动态路由**:使用 `[param].tsx` 格式,如 `[id].tsx` - **可选参数**:使用 `[[param]].tsx` 格式 - **全部匹配**:使用 `[...param].tsx` 格式 - **可选全部匹配**:使用 `[[...param]].tsx` 格式 #### 组件和工具文件命名规范 - **React 组件**:使用 PascalCase 命名,如 `UserProfile.tsx` - **工具函数**:使用 camelCase 命名,如 `dateUtils.ts` - **类型定义**:使用 camelCase 命名,如 `userTypes.ts` - **常量文件**:使用 UPPER_SNAKE_CASE 命名,如 `API_CONSTANTS.ts` **示例目录结构:** ``` src/app/(app)/land-information/ ├── layout.tsx # 布局文件 ├── page.tsx # 主页面 ├── loading.tsx # 加载状态 ├── error.tsx # 错误处理 └── archive/ ├── page.tsx # 归档页面 ├── statistics/ │ ├── page.tsx # 统计页面 │ └── components/ │ ├── FilterPanel.tsx # 组件:PascalCase │ └── statisticsReducer.tsx # 工具文件:camelCase └── [id]/ └── page.tsx # 动态路由页面 ``` **实施要点:** - 严格遵循文件命名规范,不得随意修改文件名 - 页面文件必须使用 `page.tsx`,不能使用其他名称 - 动态路由参数必须使用方括号 `[param]` 格式 - 组件和工具文件遵循通用的 JavaScript/TypeScript 命名规范 ## 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. **文档化习惯**:将开发过程中的经验和教训记录下来,形成知识积累 - 认识到文档化对团队协作和知识传承的重要性 - 建立了完整的开发规范文档体系 --- ## path:land-information/map/gis,name:GIS地图管理开发经验与问题解决 ### 总体开发经验总结 GIS地图管理页面的开发过程是一个复杂的技术集成挑战,涉及到第三方地图库的集成、异步资源加载、多层级组件交互等多个技术难点。通过这次开发,我们建立了一套完整的GIS应用开发模式,特别是在处理真实地图数据源和优雅降级方面积累了宝贵经验。 ### 问题1:地图组件初始化时缺少真实地图数据源 **问题描述:** - 初始实现的BaseMap组件只是简单的模拟展示,无法加载真实的卫星图像 - 用户反馈参考文件可以看到真实的卫星图,但当前页面只显示占位符 - 缺乏对真实地图服务商的集成支持 **原始需求分析:** - 需要支持真实的卫星影像显示,而不是简单的占位地图 - 必须支持多种地图图层切换(卫星、电子、地形、混合) - 需要完整的地图交互功能,包括缩放、平移、全屏等 **解决方案:** - 复制完整的GISMapEngine实现,支持多种地图提供商 - 实现leafletLoader动态加载器,支持异步加载地图库 - 建立真实地图瓦片数据源连接,包括ArcGIS卫星影像和OpenStreetMap **代码实现对比:** ```tsx // ❌ 初始简化实现 export class GISMapEngine { constructor(map: any) { this.map = map; // 只是一个模拟对象 } addPolygon(polygon: MapPolygon): void { this.polygons.set(polygon.id, polygon); // 没有真实渲染 } } // ✅ 最终完整实现 export class GISMapEngine { constructor(config: MapConfig) { this.provider = config.provider; this.initialize(config); // 真实初始化流程 } private async initLeaflet(config: MapConfig) { // 动态加载Leaflet库 if (!window.L) { await this.loadLeaflet(); } // 创建真实地图实例 this.map = window.L.map(this.container).setView([center[1], center[0]], zoom); // 设置真实瓦片图层 this.getLeafletLayer(layer).addTo(this.map); } } ``` ### 问题2:地图库异步加载和依赖管理复杂性 **问题描述:** - 地图库(Leaflet)需要从CDN异步加载,存在加载失败风险 - 地图组件需要在库加载完成后才能初始化,存在时序问题 - 多个地图组件可能重复加载同一资源,造成性能浪费 **原始需求分析:** - 确保地图库能够可靠加载,提供良好的用户体验 - 处理加载失败的情况,提供优雅的降级方案 - 优化资源加载性能,避免重复加载 **解决方案:** - 创建leafletLoader统一管理地图库的加载过程 - 实现加载状态管理和重试机制 - 建立全局加载状态缓存,避免重复加载 **关键实现代码:** ```tsx // leafletLoader.ts - 统一加载管理 export const preloadLeaflet = (): Promise => { return new Promise((resolve) => { if (leafletLoaded || window.L) { resolve(true); return; } if (leafletLoading) { // 等待正在进行的加载完成 const checkInterval = setInterval(() => { if (leafletLoaded || window.L) { clearInterval(checkInterval); resolve(true); } }, 100); return; } // 执行实际加载过程 const script = document.createElement('script'); script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'; script.onload = () => { leafletLoaded = true; resolve(true); }; script.onerror = () => { resolve(false); }; document.head.appendChild(script); }); }; ``` ### 问题3:地图组件与状态管理的深度集成 **问题描述:** - 地图组件需要与useReducer状态管理深度集成 - 地图的异步初始化过程与React生命周期存在冲突 - 地图事件回调与状态更新的时序同步问题 **原始需求分析:** - 地图操作需要能够更新全局状态(如选中地块、图层切换) - 状态变化需要能够反映到地图显示上 - 需要处理地图组件的清理和资源释放 **解决方案:** - 使用useImperativeHandle暴露地图实例方法给父组件 - 实现地图引擎的引用管理,确保状态同步 - 建立完整的生命周期管理,包括组件卸载时的资源清理 **状态管理集成代码:** ```tsx // BaseMap组件中的状态集成 useImperativeHandle(ref, () => ({ getMapEngine: () => mapEngineRef.current, addMarker: (marker: Marker) => { mapEngineRef.current?.addMarker(marker); }, addPolygon: (polygon: Polygon) => { mapEngineRef.current?.addPolygon(polygon); }, setCenter: (position: MapPosition, zoom?: number) => { mapEngineRef.current?.setCenter(position, zoom); }, })); // 组件卸载时的资源清理 useEffect(() => { return () => { if (mapEngineRef.current) { mapEngineRef.current.destroy(); } }; }, []); ``` ### 问题4:真实地图数据源的集成和配置 **问题描述:** - 需要集成多种真实的地图瓦片数据源 - 不同地图服务商的API格式和坐标系统存在差异 - 需要处理地图瓦片的加载性能和缓存策略 **原始需求分析:** - 提供真实的卫星影像、电子地图、地形图等多种图层 - 确保地图数据的准确性和时效性 - 优化地图加载性能,提供流畅的用户体验 **解决方案:** - 集成多个开源地图数据源,确保服务的可靠性 - 统一不同数据源的坐标系统和API格式 - 实现智能的图层切换和缓存机制 **地图数据源配置:** ```tsx private getLeafletLayer(layer: MapLayer) { const baseLayers: Record = { // ArcGIS卫星影像 - 真实卫星图 satellite: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', // OpenStreetMap - 开源电子地图 street: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', // OpenTopoMap - 开源地形图 terrain: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', // 混合图层使用卫星影像作为基础 hybrid: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', }; return window.L.tileLayer(baseLayers[layer], { attribution: '© OpenStreetMap contributors', maxZoom: 18, }); } ``` ### 问题5:地图交互功能的完整实现 **问题描述:** - 需要实现地块多边形的渲染和交互 - 地图标记点的添加和点击事件处理 - 地图控件(缩放、图层切换、全屏等)的集成 **原始需求分析:** - 地块需要在地图上以彩色多边形形式显示 - 点击地块需要触发选择事件和状态更新 - 提供完整的地图导航和操作功能 **解决方案:** - 使用地图引擎的Polygon和Marker API实现地块渲染 - 建立事件处理机制,连接地图交互和状态管理 - 集成完整的地图控件套件,提供专业级用户体验 **交互功能实现:** ```tsx // 地块多边形渲染 const polygon: Polygon = { id: field.id, path: field.coordinates, fillColor: field.color, strokeColor: field.color, fillOpacity: 0.3, strokeWeight: 2, onClick: () => { onFieldSelect(field); // 更新全局状态 toast.success(`已选择: ${field.name}`); }, }; engine.addPolygon(polygon); // 地块标记点渲染 const marker: Marker = { id: `marker-${field.id}`, position: { lat: centerLat, lng: centerLng }, title: field.name, color: field.color, onClick: () => { onFieldSelect(field); toast.success(`已选择: ${field.name}`); }, }; engine.addMarker(marker); ``` ## 开发经验对比总结 ### 与原始要求的差异分析 | 原始要求 | 实际实现 | 差异说明 | 解决过程 | |---------|---------|---------|---------| | 1:1还原地图功能 | 完整实现 + 真实数据源 | 需要集成真实地图服务商和瓦片数据 | 建立完整的地图引擎架构,支持多种数据源 | | 第三方库集成 | 专业级集成 | 需要处理异步加载、错误处理、性能优化 | 实现统一加载器和优雅降级机制 | | 组件状态管理 | 深度集成 + 生命周期管理 | 地图组件与React状态系统需要深度集成 | 使用useImperativeHandle和引用管理 | | 交互功能实现 | 完整交互套件 | 需要实现多边形、标记、控件等完整功能 | 集成地图引擎API,建立事件处理机制 | ### 关键学习点和改进 1. **第三方库集成思维**:学会了如何可靠地集成和管理复杂的第三方JavaScript库 - 掌握了异步加载、错误处理、优雅降级的完整流程 - 理解了库版本管理和兼容性处理的重要性 2. **地图API应用经验**:深入了解了Web地图开发的技术栈和最佳实践 - 学会了瓦片地图的原理和多种数据源的使用 - 掌握了地图交互事件的处理和状态同步机制 3. **React高级模式应用**:在复杂组件中应用了useImperativeHandle、useRef等高级React模式 - 深入理解了React组件的暴露方法和引用传递机制 - 掌握了复杂组件生命周期管理的最佳实践 4. **性能优化意识**:建立了地图应用的性能优化思维 - 学会了资源懒加载和缓存策略的设计 - 理解了大型第三方库对应用性能的影响和优化方法 5. **用户体验设计**:在技术实现中始终考虑用户体验 - 建立了加载状态和错误处理的设计模式 - 掌握了优雅降级和渐进增强的实现方法 6. **架构设计能力**:设计了可扩展的地图应用架构 - 建立了插件化的地图引擎设计 --- ## path:src/app/(app)/land-information/map/draw,name:数字化绘制与编辑页面开发经验 ### 1. **复杂状态管理设计** - **useReducer 模式应用**:使用 useReducer 管理复杂的编辑状态,包含多个布尔状态、数组和对象 - **状态结构设计**:设计了包含高级编辑器状态、活动标签页、地块数据、保存对话框等的状态结构 - **Action 设计模式**:采用类型安全的 Action 设计,支持状态更新、字段管理、对话框控制等操作 ### 2. **组件化架构设计** - **模块化组件结构**:将复杂功能拆分为6个独立组件,每个组件负责单一职责 - `drawEditReducer.tsx`:状态管理核心 - `DrawingTools.tsx`:绘制工具组件 - `EditingTools.tsx`:编辑工具组件 - `FieldEntryDialog.tsx`:地块信息录入对话框 - `UsageGuide.tsx`:使用指南组件 - `AdvancedEditorPromo.tsx`:高级编辑器推广组件 - **组件通信设计**:通过 props 和回调函数实现组件间的数据传递和事件处理 ### 3. **Canvas 绘图技术实现** - **多种绘制模式**:实现点、线、多边形、矩形等多种绘制模式 - **实时交互反馈**:支持鼠标移动吸附、节点高亮、实时预览等功能 - **几何计算算法**: - Shoelace 公式计算多边形面积 - 坐标距离计算周长 - 点在多边形内判断算法 - 自相交检测算法 ### 4. **高级编辑功能实现** - **节点编辑**:支持拖拽节点、添加节点、删除节点 - **地块分割**:绘制分割线将地块分成两部分,支持垂直和水平分割 - **地块合并**:多地块选择和凸包算法合并 - **历史记录管理**:实现撤销/重做功能,支持操作历史追踪 ### 5. **用户体验设计** - **分步骤操作引导**:为复杂操作提供详细的操作步骤说明 - **实时状态反馈**:Toast 通知、状态栏显示、操作确认等 - **键盘快捷键支持**:Ctrl+Z 撤销、Ctrl+S 保存、Delete 清除、Esc 取消 - **视觉状态管理**:选中高亮、禁用状态、加载状态等 ### 6. **数据管理与持久化** - **表单验证设计**:完整的表单验证逻辑,支持必填项检查和格式验证 - **本地存储集成**:与 localStorage 集成,支持地块数据的持久化 - **自动数据生成**:地块编号、名称的自动生成逻辑 - **标签管理功能**:支持标签的添加、删除和展示 ### 7. **技术规范遵循** - **shadcn/ui 语义样式**:使用 `bg-card`、`bg-muted`、`text-muted-foreground` 等语义化样式 - **暗色主题支持**:完整支持暗色主题,使用 `dark:` 前缀 - **TypeScript 类型安全**:完整的类型定义,确保类型安全 - **响应式设计**:支持不同屏幕尺寸的适配 ### 8. **开发效率提升** - **组件复用设计**:通用组件可在其他页面复用 - **配置化参数**:画布尺寸、吸附距离等参数可配置 - **错误处理机制**:完善的错误处理和用户提示 - **代码组织结构**:清晰的文件结构和命名规范 ### 9. **性能优化考虑** - **事件处理优化**:使用 useCallback 避免不必要的重渲染 - **状态更新策略**:合理的状态更新时机和批量处理 - **Canvas 渲染优化**:减少不必要的重绘和计算 ### 10. **可扩展性设计** - **插件化架构**:编辑工具采用插件化设计,易于扩展新功能 - **接口标准化**:统一的接口设计,便于功能模块替换 - **配置化开发**:支持通过配置文件调整功能和行为 - 理解了复杂应用中的组件分层和职责划分 --- ## path:src/components/common/searchFormPagination,name:搜索、表格、分页三合一组件使用心得 ### 组件概述 SearchFormPagination 是一个高度可配置的复合组件,集成了搜索表单、数据表格和分页功能。该组件采用了现代React开发模式,通过配置驱动的方式实现复杂数据展示页面的快速开发。 ### 架构设计 #### 1. 组件层次结构 ``` SearchFormPagination (主组件) ├── SearchFormComponent (搜索表单) │ ├── Input (文本搜索框) │ └── Select (下拉选择框) ├── Card (表格容器) │ ├── Table (数据表格) │ │ ├── TableHeader (表头) │ │ └── TableBody (表体) │ └── PaginationComponent (分页组件) └── LoadingOverlay (加载遮罩) ``` #### 2. 核心文件结构 ``` src/components/common/searchFormPagination/ ├── index.ts # 主组件导出 ├── page.tsx # SearchFormPagination主组件 ├── components/ │ ├── SearchFormComponent.tsx # 搜索表单组件 │ ├── PaginationComponent.tsx # 分页组件 │ └── searchFormPaginationReducer.tsx # 状态管理(可选) ``` ### 核心功能特性 #### 1. 搜索表单功能 **防抖搜索机制**: ```tsx // 关键实现:300ms防抖,避免频繁API调用 useEffect(() => { const timer = setTimeout(() => { onFiltersChangeRef.current(localFilters); }, 300); return () => clearTimeout(timer); }, [localFilters]); ``` **多字段配置支持**: ```tsx const searchFields: SearchFieldConfig[] = [ { key: 'search', type: 'text', placeholder: '搜索企业名称、编码...', }, { key: 'audit_status', type: 'select', defaultValue: 'all', options: [ { value: 'all', label: '全部状态' }, { value: 'draft', label: '草稿' }, // ...更多选项 ], }, ]; ``` #### 2. 表格展示功能 **动态列配置**: ```tsx const columns: TableColumnConfig[] = [ { key: 'name', label: '企业名称', sortable: true, // 支持排序 render: (value, row) => (
{value}
), }, { key: 'status', label: '状态', render: (value) => getStatusBadge(value), // 自定义渲染 }, ]; ``` **加载状态处理**: ```tsx // 表格加载遮罩 - 提升用户体验 {loading && (
加载中...
)} ``` #### 3. 分页功能 **完整分页配置**: ```tsx interface PaginationConfig { page: number; size: number; total: number; totalPages: number; hasNext: boolean; hasPrev: boolean; } ``` **智能分页逻辑**: - 当只有一页数据时,分页按钮隐藏但每页条数选择器仍显示 - 支持页码跳转和快速导航 - 分页操作时保持当前搜索条件 ### 使用示例 #### 完整调用示例 ```tsx import { SearchFormPagination } from '@/components/common/searchFormPagination'; export default function EnterpriseManagement() { const [enterprises, setEnterprises] = useState([]); const [loading, setLoading] = useState(false); const [pagination, setPagination] = useState({ page: 1, size: 10, total: 0, totalPages: 0, hasNext: false, hasPrev: false, }); // 搜索字段配置 const searchFields = [ { key: 'search', label: '搜索', type: 'text', placeholder: '搜索企业名称、编码...', }, { key: 'audit_status', label: '审核状态', type: 'select', defaultValue: 'all', options: [ { value: 'all', label: '全部状态' }, { value: 'draft', label: '草稿' }, { value: 'pending', label: '待审核' }, { value: 'approved', label: '审核通过' }, ], }, ]; // 表格列配置 const columns = [ { key: 'name', label: '企业名称', sortable: true, render: (value) =>
{value}
, }, { key: 'auditStatus', label: '审核状态', render: (value) => getAuditStatusBadge(value), }, { key: 'actions', label: '操作', render: (_, row) => (
), }, ]; // 数据加载函数 const loadEnterprises = useCallback(async (params) => { try { setLoading(true); const response = await fetchTenants(params); setEnterprises(response.data); setPagination({ page: response.page, size: response.size, total: response.total, totalPages: response.total_pages, hasNext: response.has_next, hasPrev: response.has_prev, }); } catch (error) { console.error('Failed to load enterprises:', error); } finally { setLoading(false); } }, []); // 搜索处理 const handleSearch = useCallback((filters) => { loadEnterprises({ filters, pagination: { page: 1, size: pagination.size }, }); }, [loadEnterprises, pagination.size]); // 分页处理 const handlePageChange = useCallback((page) => { setPagination(prev => ({ ...prev, page })); loadEnterprises({ pagination: { page, size: pagination.size }, filters: searchFilters, }); }, [loadEnterprises, pagination.size]); return (
新建企业} searchFields={searchFields} columns={columns} data={enterprises} loading={loading} error={null} pagination={pagination} onPageChange={handlePageChange} onSizeChange={handleSizeChange} onSearch={handleSearch} emptyIcon={} emptyText="暂无企业数据" />
); } ``` ### 接口定义 #### SearchFieldConfig - 搜索字段配置 ```tsx interface SearchFieldConfig { key: string; // 字段标识 label: string; // 显示标签 type: 'text' | 'select'; // 字段类型 placeholder?: string; // 占位符文本 options?: Array<{ value: string; label: string }>; // 下拉选项 defaultValue?: string; // 默认值 } ``` #### TableColumnConfig - 表格列配置 ```tsx interface TableColumnConfig { key: string; // 数据字段名 label: string; // 表头显示文本 sortable?: boolean; // 是否支持排序 width?: string; // 列宽设置 render?: (value: any, row: any, index: number) => React.ReactNode; // 自定义渲染 } ``` #### SearchFormPaginationProps - 主组件属性 ```tsx interface SearchFormPaginationProps { // 搜索配置 searchFields: SearchFieldConfig[]; onSearch?: (filters: Record) => void; // 表格配置 columns: TableColumnConfig[]; data?: T[]; loading?: boolean; error?: string | null; // 分页配置 pagination?: PaginationConfig; onPageChange?: (page: number) => void; onSizeChange?: (size: number) => void; onSort?: (sortBy: string, sortOrder: 'asc' | 'desc') => void; // UI配置 formTitle?: string; formRightContent?: React.ReactNode; emptyIcon?: React.ReactNode; emptyText?: string; showSizeSelector?: boolean; showPageInfo?: boolean; } ``` ### 最佳实践 #### 1. 性能优化 **使用 useCallback 优化函数引用**: ```tsx // ✅ 正确做法:使用 useCallback 避免重复渲染 const handleSearch = useCallback((filters) => { loadEnterprises({ filters }); }, [loadEnterprises]); const handlePageChange = useCallback((page) => { loadEnterprises({ page, size: pagination.size }); }, [loadEnterprises, pagination.size]); // ❌ 错误做法:每次渲染都创建新函数 const handleSearch = (filters) => { loadEnterprises({ filters }); }; ``` **统一数据重载函数**: ```tsx // ✅ 推荐:统一的数据重载逻辑,避免代码重复 const reloadData = useCallback(() => { const reloadParams = { filters: searchFilters, pagination: { page: pagination.page, size: pagination.size } }; loadEnterprises(reloadParams); }, [loadEnterprises, searchFilters, pagination]); // 在多个地方使用 const handleCreateSuccess = () => reloadData(); const confirmStatusChange = async () => { await enableTenant(tenantId); reloadData(); }; ``` #### 2. 状态管理 **合理的状态依赖**: ```tsx // ✅ 正确:包含所有必要的依赖 const handlePageChange = useCallback((page: number) => { loadEnterprises({ filters: searchFilters, // 确保搜索条件不会丢失 pagination: { page, size: pagination.size } }); }, [loadEnterprises, searchFilters, pagination.size]); ``` #### 3. 错误处理 **完善的错误状态处理**: ```tsx ``` #### 4. 扩展性设计 **新增字段的简单步骤**: 1. 在 `searchFields` 数组中添加新配置 2. 确保后端API支持新的查询参数 3. 无需修改任何组件逻辑 ```tsx // 添加新的下拉框只需一行配置 { key: 'enterprise_status', label: '企业状态', type: 'select', defaultValue: 'all', options: [ { value: 'all', label: '全部状态' }, { value: 'active', label: '启用' }, { value: 'inactive', label: '禁用' }, ], } ``` ### 技术特点 #### 1. 类型安全 - 完整的 TypeScript 类型定义 - 泛型支持,确保数据类型一致性 - 严格的接口约束 #### 2. 用户体验优化 - 防抖搜索,避免频繁请求 - 加载状态遮罩,提供视觉反馈 - 分页状态保持,避免搜索条件丢失 - 响应式设计,适配不同屏幕尺寸 #### 3. 可维护性 - 配置驱动,减少硬编码 - 组件化设计,职责单一 - 完善的错误处理机制 #### 4. 可扩展性 - 插件化的字段配置 - 自定义渲染函数支持 - 多种配置选项 ### 常见问题解决 #### 1. 分页后搜索条件丢失 **问题**:切换页码或每页条数时,搜索条件被重置 **解决方案**: ```tsx const handlePageChange = useCallback((page) => { // 确保传递当前的搜索条件 loadEnterprises({ filters: searchFilters, // 关键:传递搜索条件 pagination: { page, size: pagination.size } }); }, [loadEnterprises, searchFilters, pagination.size]); ``` #### 2. 频繁API调用问题 **问题**:用户快速输入时触发过多API请求 **解决方案**:组件内置300ms防抖机制,无需额外处理 #### 3. 加载状态处理 **问题**:数据加载时用户体验不佳 **解决方案**: ```tsx ``` ### 性能优化最佳实践 #### 1. 事件驱动模式 **原则**:避免使用setTimeout,尽可能减少useEffect,使用事件驱动来实现状态更新。 **最佳实践**: ```tsx // ❌ 避免写法:使用setTimeout和过多useEffect useEffect(() => { const timer = setTimeout(() => { loadData(); }, 300); return () => clearTimeout(timer); }, [filters]); useEffect(() => { if (page > 1) { loadData(); } }, [page]); // ✅ 推荐写法:事件驱动,直接调用 const handleSearch = useCallback((filters) => { setSearchFilters(filters); loadData({ filters, pagination: { page: 1, size: pagination.size } }); }, [loadData, pagination.size]); const handlePageChange = useCallback((page) => { setPagination(prev => ({ ...prev, page })); loadData({ filters: searchFilters, pagination: { page, size: pagination.size } }); }, [loadData, searchFilters, pagination.size]); ``` #### 2. 函数依赖优化 **原则**:减少useCallback和useMemo的依赖项,通过参数传递而非依赖外部状态。 **最佳实践**: ```tsx // ❌ 避免写法:过多依赖项导致函数频繁重新创建 const loadData = useCallback(async () => { // 依赖filters, pagination, sortBy等 }, [filters, pagination, sortBy]); // ✅ 推荐写法:无依赖项,通过参数传递 const loadData = useCallback(async (params) => { // 使用params.filters, params.pagination等 }, []); // 空依赖数组 ``` #### 3. 搜索防抖优化 **原则**:下拉框选择立即触发,文本输入使用防抖,避免不必要的延迟。 **实现方式**: ```tsx // SearchFormComponent中的优化实现 const handleInputChange = (key: string, value: string, fieldType: 'text' | 'select') => { const newFilters = { ...localFilters, [key]: value }; setLocalFilters(newFilters); // 下拉框选择立即触发查询,文本输入使用防抖 if (fieldType === 'select') { onFiltersChangeRef.current(newFilters); // 立即执行 } // 文本输入的防抖在useEffect中处理 }; ``` ### 组件设计原则 #### 1. 排序功能简化 **设计决策**:SearchFormPagination组件不再支持表头排序功能。 **原因**: - 简化组件复杂度,提高性能 - 减少不必要的交互,专注核心功能(搜索、展示、分页) - 避免排序逻辑与业务逻辑耦合 **替代方案**: - 如需排序功能,在业务页面层面实现 - 通过下拉框或其他UI控件提供排序选项 #### 2. 接口简化 **删除的排序相关接口**: ```tsx // ❌ 已删除的接口 interface TableColumnConfig { sortable?: boolean; // 删除 // ... } interface SearchFormPaginationProps { sortBy?: string; // 删除 sortOrder?: 'asc' | 'desc'; // 删除 onSort?: (sortBy: string, sortOrder: 'asc' | 'desc') => void; // 删除 // ... } ``` #### 3. 表头渲染简化 **删除的排序交互**: - 删除了表头的点击事件处理 - 删除了排序箭头图标显示 - 删除了鼠标悬停样式效果 ### 重构指南 如果要重构或基于此组件开发新功能,请遵循以下原则: 1. **保持接口兼容性**:不要破坏现有的props接口 2. **扩展而非修改**:通过新的配置项而非修改现有逻辑来添加功能 3. **类型安全**:确保所有新功能都有完整的TypeScript类型定义 4. **测试覆盖**:新功能应该有相应的测试用例 5. **文档更新**:及时更新使用文档和接口说明 6. **性能优先**:采用事件驱动模式,避免不必要的useEffect和setTimeout 7. **功能专注**:保持组件职责单一,避免功能过度复杂化 ### 总结 SearchFormPagination 组件通过配置驱动的方式,极大地简化了复杂数据展示页面的开发工作。其核心优势在于: - **高度可配置**:通过配置而非代码实现功能定制 - **性能优化**:事件驱动模式,无setTimeout依赖,最小化useEffect使用 - **用户体验**:下拉框立即响应,文本输入智能防抖 - **易于扩展**:新增功能只需要修改配置,无需修改组件逻辑 - **类型安全**:完整的 TypeScript 支持 - **功能专注**:专注搜索、展示、分页核心功能,避免过度设计 该组件可以作为项目中所有数据展示页面的标准解决方案,显著提升开发效率和代码质量。