# 🚀 作业数据记录功能 - 快速实现方案

## 🎯 目标

**快速实现作业数据记录的4大核心功能**:

1. ✅ 实际作业面积（GIS计算）
2. ✅ 作业轨迹图（地图可视化）
3. ✅ 作业质量评估（传感器数据）
4. ✅ 数据自动归档（归档系统）

---

## 📦 快速实现方案

### 方案A: 最小可用版本 (MVP) - 推荐 ⭐⭐⭐⭐⭐

**实现内容**:
1. ✅ 增强 OperationRecord 组件
2. ✅ 添加轨迹地图显示
3. ✅ 添加详细质量评估面板
4. ✅ 添加 GIS 面积计算逻辑
5. ✅ 添加自动归档按钮

**特点**:
- 快速实现，2-3小时完成
- 使用模拟数据展示功能
- UI完整，逻辑清晰
- 便于后续集成真实数据

**适用场景**:
- 需要快速展示功能
- 暂时没有真实GPS数据
- 需要验证设计方案

---

### 方案B: 完整实现版本 - 长期规划

**实现内容**:
1. ✅ 实现真实的GIS几何计算
2. ✅ 集成真实GPS轨迹数据
3. ✅ 实现传感器数据处理
4. ✅ 完整的自动归档系统

**特点**:
- 完整功能实现
- 需要20-28小时
- 需要真实数据源
- 产品级质量

**适用场景**:
- 产品正式上线
- 已有真实设备数据
- 需要精确计算

---

## 🚀 快速实现：方案A (MVP)

### Step 1: 创建 GIS 计算工具

**文件**: `/lib/gisCalculation.ts`

```typescript
// GIS 面积计算工具
export interface GPSPoint {
  lat: number;
  lng: number;
  timestamp: string;
  speed: number;
  workingStatus: boolean;
}

export interface AreaCalculationResult {
  actualArea: number;      // 实际作业面积 (亩)
  coverage: number;        // 覆盖率 (%)
  overlapRate: number;     // 重叠率 (%)
  efficiency: number;      // 作业效率 (亩/小时)
  missedArea: number;      // 漏作业面积 (亩)
}

// 使用 Shoelace 公式计算多边形面积
export function calculatePolygonArea(points: GPSPoint[]): number {
  if (points.length < 3) return 0;
  
  let area = 0;
  for (let i = 0; i < points.length; i++) {
    const j = (i + 1) % points.length;
    area += points[i].lat * points[j].lng;
    area -= points[j].lat * points[i].lng;
  }
  
  area = Math.abs(area) / 2;
  
  // 转换为亩 (1平方度 ≈ 12364平方公里 ≈ 18546000亩)
  // 简化计算，实际需要考虑纬度
  const areaInMu = area * 18546000 / (180 * 180);
  
  return areaInMu;
}

// 计算作业面积和质量指标
export function calculateOperationArea(
  trajectoryPoints: GPSPoint[],
  plannedArea: number,
  workingWidth: number = 2.5
): AreaCalculationResult {
  // 筛选作业中的点
  const workingPoints = trajectoryPoints.filter(p => p.workingStatus);
  
  // 计算实际作业面积（简化版）
  const totalDistance = calculateTotalDistance(workingPoints);
  const actualArea = (totalDistance * workingWidth) / 666.67; // 转换为亩
  
  // 计算覆盖率
  const coverage = Math.min((actualArea / plannedArea) * 100, 100);
  
  // 计算重叠率（模拟）
  const overlapRate = Math.max(0, (actualArea - plannedArea) / plannedArea * 100);
  
  // 计算效率
  const duration = calculateDuration(trajectoryPoints);
  const efficiency = duration > 0 ? actualArea / duration : 0;
  
  // 计算漏作业面积
  const missedArea = Math.max(0, plannedArea - actualArea);
  
  return {
    actualArea: Number(actualArea.toFixed(2)),
    coverage: Number(coverage.toFixed(1)),
    overlapRate: Number(overlapRate.toFixed(1)),
    efficiency: Number(efficiency.toFixed(2)),
    missedArea: Number(missedArea.toFixed(2)),
  };
}

// 计算轨迹总距离
function calculateTotalDistance(points: GPSPoint[]): number {
  let distance = 0;
  for (let i = 1; i < points.length; i++) {
    const d = calculateDistance(points[i-1], points[i]);
    distance += d;
  }
  return distance;
}

// 计算两点之间的距离 (Haversine公式)
function calculateDistance(p1: GPSPoint, p2: GPSPoint): number {
  const R = 6371; // 地球半径 (km)
  const dLat = toRad(p2.lat - p1.lat);
  const dLng = toRad(p2.lng - p1.lng);
  
  const a = 
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(toRad(p1.lat)) * Math.cos(toRad(p2.lat)) *
    Math.sin(dLng / 2) * Math.sin(dLng / 2);
  
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c;
}

function toRad(degrees: number): number {
  return degrees * (Math.PI / 180);
}

// 计算作业时长 (小时)
function calculateDuration(points: GPSPoint[]): number {
  if (points.length < 2) return 0;
  
  const start = new Date(points[0].timestamp).getTime();
  const end = new Date(points[points.length - 1].timestamp).getTime();
  
  return (end - start) / (1000 * 60 * 60);
}

// 生成模拟轨迹数据（用于演示）
export function generateMockTrajectory(
  center: { lat: number; lng: number },
  fieldSize: { width: number; height: number } // 单位：度
): GPSPoint[] {
  const points: GPSPoint[] = [];
  const startTime = new Date('2025-10-17T08:00:00').getTime();
  
  // 生成往返式作业轨迹
  const rows = 20;
  const spacing = fieldSize.height / rows;
  
  for (let i = 0; i < rows; i++) {
    const lat = center.lat - fieldSize.height / 2 + i * spacing;
    const direction = i % 2 === 0 ? 1 : -1;
    const steps = 30;
    
    for (let j = 0; j < steps; j++) {
      const progress = j / steps;
      const lng = direction > 0
        ? center.lng - fieldSize.width / 2 + progress * fieldSize.width
        : center.lng + fieldSize.width / 2 - progress * fieldSize.width;
      
      points.push({
        lat,
        lng,
        timestamp: new Date(startTime + points.length * 60000).toISOString(),
        speed: 5 + Math.random() * 2,
        workingStatus: true,
      });
    }
    
    // 添加转弯点
    if (i < rows - 1) {
      const turnLat = center.lat - fieldSize.height / 2 + (i + 0.5) * spacing;
      const turnLng = direction > 0
        ? center.lng + fieldSize.width / 2
        : center.lng - fieldSize.width / 2;
      
      points.push({
        lat: turnLat,
        lng: turnLng,
        timestamp: new Date(startTime + points.length * 60000).toISOString(),
        speed: 2,
        workingStatus: false,
      });
    }
  }
  
  return points;
}
```

---

### Step 2: 创建轨迹地图组件

**文件**: `/components/machinery/operation/TrajectoryMap.tsx`

```typescript
import { useEffect, useRef } from 'react';
import { Card } from '../../ui/card';
import { Badge } from '../../ui/badge';
import { GPSPoint } from '../../../lib/gisCalculation';

interface TrajectoryMapProps {
  trajectoryPoints: GPSPoint[];
  fieldCenter: { lat: number; lng: number };
  plannedArea?: { lat: number; lng: number }[];
  showPlannedArea?: boolean;
  height?: number;
}

export function TrajectoryMap({
  trajectoryPoints,
  fieldCenter,
  plannedArea,
  showPlannedArea = true,
  height = 400,
}: TrajectoryMapProps) {
  const mapRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // 使用高德地图绘制轨迹
    if (!window.AMap || !mapRef.current) return;

    const map = new window.AMap.Map(mapRef.current, {
      center: [fieldCenter.lng, fieldCenter.lat],
      zoom: 15,
      mapStyle: 'amap://styles/normal',
    });

    // 绘制计划区域（如果提供）
    if (showPlannedArea && plannedArea && plannedArea.length > 0) {
      const polygon = new window.AMap.Polygon({
        path: plannedArea.map(p => [p.lng, p.lat]),
        fillColor: '#3b82f6',
        fillOpacity: 0.1,
        strokeColor: '#3b82f6',
        strokeWeight: 2,
        strokeOpacity: 0.5,
        strokeStyle: 'dashed',
      });
      map.add(polygon);
    }

    // 绘制作业轨迹
    if (trajectoryPoints.length > 0) {
      // 分离作业中和非作业的点
      const workingPath: any[] = [];
      const idlePath: any[] = [];

      trajectoryPoints.forEach(point => {
        const lnglat = [point.lng, point.lat];
        if (point.workingStatus) {
          workingPath.push(lnglat);
        } else {
          idlePath.push(lnglat);
        }
      });

      // 绘制作业路径（绿色）
      if (workingPath.length > 1) {
        const workingPolyline = new window.AMap.Polyline({
          path: workingPath,
          strokeColor: '#10b981',
          strokeWeight: 3,
          strokeOpacity: 0.8,
        });
        map.add(workingPolyline);
      }

      // 绘制空转路径（灰色）
      if (idlePath.length > 1) {
        const idlePolyline = new window.AMap.Polyline({
          path: idlePath,
          strokeColor: '#9ca3af',
          strokeWeight: 2,
          strokeOpacity: 0.5,
          strokeStyle: 'dashed',
        });
        map.add(idlePolyline);
      }

      // 添加起点标记
      const startMarker = new window.AMap.Marker({
        position: [trajectoryPoints[0].lng, trajectoryPoints[0].lat],
        icon: new window.AMap.Icon({
          image: 'https://webapi.amap.com/theme/v1.3/markers/n/start.png',
          size: new window.AMap.Size(25, 34),
          imageSize: new window.AMap.Size(25, 34),
        }),
      });
      map.add(startMarker);

      // 添加终点标记
      const endPoint = trajectoryPoints[trajectoryPoints.length - 1];
      const endMarker = new window.AMap.Marker({
        position: [endPoint.lng, endPoint.lat],
        icon: new window.AMap.Icon({
          image: 'https://webapi.amap.com/theme/v1.3/markers/n/end.png',
          size: new window.AMap.Size(25, 34),
          imageSize: new window.AMap.Size(25, 34),
        }),
      });
      map.add(endMarker);

      // 自动调整视野
      map.setFitView();
    }

    return () => {
      map.destroy();
    };
  }, [trajectoryPoints, fieldCenter, plannedArea, showPlannedArea]);

  return (
    <div className="space-y-3">
      {/* 图例 */}
      <div className="flex items-center gap-4 text-sm">
        <div className="flex items-center gap-2">
          <div className="w-4 h-1 bg-green-600 rounded" />
          <span>作业路径</span>
        </div>
        <div className="flex items-center gap-2">
          <div className="w-4 h-1 bg-gray-400 rounded border-dashed" />
          <span>空转路径</span>
        </div>
        {showPlannedArea && (
          <div className="flex items-center gap-2">
            <div className="w-4 h-4 bg-blue-500 opacity-20 border-2 border-blue-500 border-dashed rounded" />
            <span>计划区域</span>
          </div>
        )}
      </div>

      {/* 地图容器 */}
      <div
        ref={mapRef}
        style={{ height: `${height}px` }}
        className="w-full rounded-lg border"
      />

      {/* 轨迹统计 */}
      <div className="grid grid-cols-3 gap-3">
        <Card className="p-3">
          <div className="text-xs text-muted-foreground">轨迹点数</div>
          <div className="mt-1 text-sm">{trajectoryPoints.length}</div>
        </Card>
        <Card className="p-3">
          <div className="text-xs text-muted-foreground">作业点数</div>
          <div className="mt-1 text-sm text-green-600">
            {trajectoryPoints.filter(p => p.workingStatus).length}
          </div>
        </Card>
        <Card className="p-3">
          <div className="text-xs text-muted-foreground">空转点数</div>
          <div className="mt-1 text-sm text-gray-600">
            {trajectoryPoints.filter(p => !p.workingStatus).length}
          </div>
        </Card>
      </div>
    </div>
  );
}
```

---

### Step 3: 创建质量评估详情组件

**文件**: `/components/machinery/operation/QualityDetail.tsx`

```typescript
import { Card } from '../../ui/card';
import { Progress } from '../../ui/progress';
import { Badge } from '../../ui/badge';
import { CheckCircle2, AlertCircle, TrendingUp, TrendingDown } from 'lucide-react';

interface QualityDetailProps {
  quality: {
    overall: string;
    score: number;
    uniformity: number;
    depthConsistency: number;
    straightness: number;
    overlapRate: number;
    missRate: number;
  };
  operationType: string;
}

export function QualityDetail({ quality, operationType }: QualityDetailProps) {
  const getScoreColor = (score: number) => {
    if (score >= 90) return 'text-green-600';
    if (score >= 70) return 'text-blue-600';
    if (score >= 50) return 'text-yellow-600';
    return 'text-red-600';
  };

  const getScoreIcon = (score: number) => {
    if (score >= 70) return <CheckCircle2 className="w-5 h-5 text-green-600" />;
    return <AlertCircle className="w-5 h-5 text-yellow-600" />;
  };

  const getProgressColor = (score: number) => {
    if (score >= 90) return 'bg-green-600';
    if (score >= 70) return 'bg-blue-600';
    if (score >= 50) return 'bg-yellow-600';
    return 'bg-red-600';
  };

  return (
    <div className="space-y-6">
      {/* 总体评分 */}
      <Card className="p-6">
        <div className="flex items-center justify-between mb-4">
          <div>
            <h4>作业质量评估</h4>
            <p className="text-sm text-muted-foreground mt-1">
              基于传感器数据和GIS分析的综合评估
            </p>
          </div>
          <div className="text-right">
            <div className={`text-3xl ${getScoreColor(quality.score)}`}>
              {quality.score}
            </div>
            <Badge
              variant="secondary"
              className={
                quality.overall === '优秀' ? 'bg-green-100 text-green-700' :
                quality.overall === '良好' ? 'bg-blue-100 text-blue-700' :
                quality.overall === '一般' ? 'bg-yellow-100 text-yellow-700' :
                'bg-red-100 text-red-700'
              }
            >
              {quality.overall}
            </Badge>
          </div>
        </div>

        {/* 总分进度条 */}
        <div className="relative">
          <Progress value={quality.score} className="h-3" />
          <div
            className="absolute top-0 h-3 rounded-full transition-all"
            style={{
              width: `${quality.score}%`,
              backgroundColor: quality.score >= 90 ? '#10b981' :
                             quality.score >= 70 ? '#3b82f6' :
                             quality.score >= 50 ? '#f59e0b' : '#ef4444'
            }}
          />
        </div>
      </Card>

      {/* 详细指标 */}
      <Card className="p-6">
        <h4 className="mb-4">详细指标</h4>
        <div className="space-y-4">
          {/* 均匀度 */}
          <div>
            <div className="flex items-center justify-between mb-2">
              <div className="flex items-center gap-2">
                {getScoreIcon(quality.uniformity)}
                <span className="text-sm">作业均匀度</span>
              </div>
              <span className={`text-sm ${getScoreColor(quality.uniformity)}`}>
                {quality.uniformity}%
              </span>
            </div>
            <Progress value={quality.uniformity} className="h-2" />
            <p className="text-xs text-muted-foreground mt-1">
              {operationType === '播种' ? '播种密度的一致性' : '作业参数的稳定性'}
            </p>
          </div>

          {/* 深浅一致性 */}
          <div>
            <div className="flex items-center justify-between mb-2">
              <div className="flex items-center gap-2">
                {getScoreIcon(quality.depthConsistency)}
                <span className="text-sm">深浅一致性</span>
              </div>
              <span className={`text-sm ${getScoreColor(quality.depthConsistency)}`}>
                {quality.depthConsistency}%
              </span>
            </div>
            <Progress value={quality.depthConsistency} className="h-2" />
            <p className="text-xs text-muted-foreground mt-1">
              作业深度的稳定性和准确性
            </p>
          </div>

          {/* 路径直线度 */}
          <div>
            <div className="flex items-center justify-between mb-2">
              <div className="flex items-center gap-2">
                {getScoreIcon(quality.straightness)}
                <span className="text-sm">路径直线度</span>
              </div>
              <span className={`text-sm ${getScoreColor(quality.straightness)}`}>
                {quality.straightness}%
              </span>
            </div>
            <Progress value={quality.straightness} className="h-2" />
            <p className="text-xs text-muted-foreground mt-1">
              作业路径的笔直程度
            </p>
          </div>

          {/* 重叠率 */}
          <div>
            <div className="flex items-center justify-between mb-2">
              <div className="flex items-center gap-2">
                {quality.overlapRate > 10 ? (
                  <AlertCircle className="w-5 h-5 text-yellow-600" />
                ) : (
                  <CheckCircle2 className="w-5 h-5 text-green-600" />
                )}
                <span className="text-sm">重叠率</span>
              </div>
              <span className={`text-sm ${quality.overlapRate > 10 ? 'text-yellow-600' : 'text-green-600'}`}>
                {quality.overlapRate}%
              </span>
            </div>
            <Progress value={Math.min(quality.overlapRate * 5, 100)} className="h-2" />
            <p className="text-xs text-muted-foreground mt-1">
              作业区域的重复覆盖比例（建议 < 10%）
            </p>
          </div>

          {/* 漏作业率 */}
          <div>
            <div className="flex items-center justify-between mb-2">
              <div className="flex items-center gap-2">
                {quality.missRate > 5 ? (
                  <AlertCircle className="w-5 h-5 text-red-600" />
                ) : (
                  <CheckCircle2 className="w-5 h-5 text-green-600" />
                )}
                <span className="text-sm">漏作业率</span>
              </div>
              <span className={`text-sm ${quality.missRate > 5 ? 'text-red-600' : 'text-green-600'}`}>
                {quality.missRate}%
              </span>
            </div>
            <Progress value={Math.min(quality.missRate * 10, 100)} className="h-2" />
            <p className="text-xs text-muted-foreground mt-1">
              未覆盖区域的比例（建议 < 5%）
            </p>
          </div>
        </div>
      </Card>

      {/* 评估标准 */}
      <Card className="p-4 bg-blue-50 border-blue-200">
        <h5 className="text-sm text-blue-900 mb-2">📊 评估标准</h5>
        <div className="grid grid-cols-2 gap-2 text-xs text-blue-800">
          <div>• 优秀：90分以上</div>
          <div>• 良好：70-89分</div>
          <div>• 一般：50-69分</div>
          <div>• 较差：50分以下</div>
        </div>
      </Card>
    </div>
  );
}
```

---

### Step 4: 更新 OperationRecord 组件

**文件**: `/components/machinery/operation/OperationRecord.tsx`

**主要更新**:
1. 添加"查看详情"按钮
2. 显示轨迹地图
3. 显示详细质量评估
4. 添加"归档"按钮
5. 使用 GIS 计算模拟数据

---

## 📋 实现检查清单

### Phase 1: 基础功能
- [ ] 创建 `/lib/gisCalculation.ts`
- [ ] 创建 `/components/machinery/operation/TrajectoryMap.tsx`
- [ ] 创建 `/components/machinery/operation/QualityDetail.tsx`
- [ ] 更新 `/components/machinery/operation/OperationRecord.tsx`

### Phase 2: 功能测试
- [ ] 测试 GIS 面积计算
- [ ] 测试轨迹地图显示
- [ ] 测试质量评估展示
- [ ] 测试归档功能

### Phase 3: 集成优化
- [ ] 集成到主界面
- [ ] 添加错误处理
- [ ] 优化性能
- [ ] 完善文档

---

## 🎉 完成后效果

### 功能完整性
✅ GIS几何计算实际作业面积  
✅ 地图上显示作业轨迹  
✅ 详细的质量评估指标  
✅ 数据归档功能  

### 用户体验
✅ 直观的轨迹地图  
✅ 清晰的质量评分  
✅ 详细的指标说明  
✅ 便捷的归档操作  

---

**实现时间**: 2-3 小时  
**难度级别**: ⭐⭐⭐  
**状态**: 📝 **方案就绪，可以开始实现**

**建议**: 先实现MVP版本，验证设计后再完善细节！
