1045 lines
28 KiB
Markdown
1045 lines
28 KiB
Markdown
# 📋 任务分配功能 - 完整性检查报告
|
||
|
||
## 🎯 需求对照检查
|
||
|
||
### ✅ 已实现功能
|
||
|
||
| 需求项 | 状态 | 实现情况 |
|
||
|--------|------|---------|
|
||
| **查看农机/驾驶员状态** | ✅ 已实现 | 显示空闲、作业中、维修中状态 |
|
||
| **创建任务** | ✅ 已实现 | 完整的任务创建表单 |
|
||
| **分配任务** | ⚠️ 部分实现 | 通过表单选择,缺少拖拽功能 |
|
||
| **设置优先级** | ✅ 已实现 | 紧急、高、中、低 四级优先级 |
|
||
| **冲突检测** | ❌ 未实现 | 有占位函数但无实际逻辑 |
|
||
|
||
---
|
||
|
||
## 📋 详细功能分析
|
||
|
||
### 1️⃣ 农机/驾驶员状态查看 ✅
|
||
|
||
**实现位置**: `/components/machinery/scheduling/TaskAssignment.tsx`
|
||
|
||
**功能代码**:
|
||
```tsx
|
||
// 农机资源面板 (第335-359行)
|
||
<Card className="p-6">
|
||
<h3 className="mb-4">农机资源</h3>
|
||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||
{machinery.map(m => (
|
||
<div key={m.id} className="p-3 border rounded-lg">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<div className="text-sm">{m.name}</div>
|
||
<div className="text-xs text-muted-foreground">{m.model}</div>
|
||
</div>
|
||
<Badge variant="secondary">
|
||
{m.status || '空闲'} {/* ✅ 显示状态 */}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
|
||
// 驾驶员资源面板 (第361-386行)
|
||
<Card className="p-6">
|
||
<h3 className="mb-4">驾驶员资源</h3>
|
||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||
{drivers.map(d => (
|
||
<div key={d.id} className="p-3 border rounded-lg">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<div className="text-sm">{d.name}</div>
|
||
<div className="text-xs text-muted-foreground">{d.phone}</div>
|
||
</div>
|
||
<Badge variant="secondary">
|
||
{d.status === 'active' ? '空闲' : d.status} {/* ✅ 显示状态 */}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
```
|
||
|
||
**状态颜色编码** (第162-170行):
|
||
```tsx
|
||
const getStatusColor = (status: string) => {
|
||
switch (status) {
|
||
case '空闲': return 'bg-green-100 text-green-700'; // ✅ 绿色
|
||
case '作业中': return 'bg-blue-100 text-blue-700'; // ✅ 蓝色
|
||
case '维修中': return 'bg-orange-100 text-orange-700'; // ✅ 橙色
|
||
case '休假': return 'bg-gray-100 text-gray-700';
|
||
default: return 'bg-gray-100 text-gray-700';
|
||
}
|
||
};
|
||
```
|
||
|
||
**统计卡片** (第219-249行):
|
||
```tsx
|
||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||
<Card className="p-4">
|
||
<div className="text-sm text-muted-foreground">总任务数</div>
|
||
<div className="mt-1">{tasks.length}</div>
|
||
</Card>
|
||
<Card className="p-4">
|
||
<div className="text-sm text-muted-foreground">空闲农机</div>
|
||
<div className="mt-1 text-green-600">
|
||
{machinery.filter(m => m.status !== '维修中').length} {/* ✅ 统计空闲农机 */}
|
||
</div>
|
||
</Card>
|
||
<Card className="p-4">
|
||
<div className="text-sm text-muted-foreground">空闲驾驶员</div>
|
||
<div className="mt-1 text-green-600">
|
||
{drivers.filter(d => d.status === 'active').length} {/* ✅ 统计空闲驾驶员 */}
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
```
|
||
|
||
**评估**: ✅ **完全实现**
|
||
- 可以查看所有农机的状态
|
||
- 可以查看所有驾驶员的状态
|
||
- 有清晰的颜色区分
|
||
- 有统计信息
|
||
|
||
---
|
||
|
||
### 2️⃣ 任务创建功能 ✅
|
||
|
||
**实现位置**: `/components/machinery/scheduling/TaskForm.tsx`
|
||
|
||
**创建按钮** (TaskAssignment.tsx 第210-217行):
|
||
```tsx
|
||
<Button onClick={() => {
|
||
setEditingTask(null);
|
||
setShowTaskForm(true);
|
||
}}>
|
||
<Plus className="w-4 h-4 mr-2" />
|
||
创建任务
|
||
</Button>
|
||
```
|
||
|
||
**任务表单内容**:
|
||
```tsx
|
||
// 基本信息 (第142-204行)
|
||
- 任务名称 *
|
||
- 任务类型 * (耕地/播种/施肥/灌溉/喷药/收获/运输/其他)
|
||
- 任务描述
|
||
- 优先级 (低/中/高/紧急)
|
||
|
||
// 地块信息 (第206-258行)
|
||
- 选择地块
|
||
- 显示地块面积、位置、作物
|
||
|
||
// 时间安排 (第260-302行)
|
||
- 开始时间 *
|
||
- 结束时间 *
|
||
- 自动计算预估时长
|
||
|
||
// 资源分配 (第304-360行)
|
||
- 农机设备 (可选)
|
||
- 驾驶员 (可选)
|
||
- 未分配提示
|
||
|
||
// 作业要求 (第362-445行)
|
||
- 作业深度 (耕地/播种)
|
||
- 作业速度
|
||
- 播种量 (播种)
|
||
- 施肥量 (施肥)
|
||
- 质量要求
|
||
|
||
// 备注 (第447-456行)
|
||
- 备注信息
|
||
```
|
||
|
||
**保存处理** (TaskAssignment.tsx 第95-124行):
|
||
```tsx
|
||
const handleSaveTask = (taskData: Partial<Task>) => {
|
||
if (editingTask) {
|
||
// ✅ 更新任务
|
||
const updated: Task = {
|
||
...editingTask,
|
||
...taskData,
|
||
updatedAt: new Date().toISOString(),
|
||
updatedBy: '系统管理员',
|
||
};
|
||
const newTasks = tasks.map(t => t.id === updated.id ? updated : t);
|
||
setTasks(newTasks);
|
||
localStorage.setItem('smart_agriculture_tasks', JSON.stringify(newTasks));
|
||
toast.success('任务更新成功');
|
||
} else {
|
||
// ✅ 创建新任务
|
||
const newTask: Task = {
|
||
id: `task-${Date.now()}`,
|
||
...taskData as Task,
|
||
createdAt: new Date().toISOString(),
|
||
updatedAt: new Date().toISOString(),
|
||
createdBy: '系统管理员',
|
||
};
|
||
const newTasks = [...tasks, newTask];
|
||
setTasks(newTasks);
|
||
localStorage.setItem('smart_agriculture_tasks', JSON.stringify(newTasks));
|
||
toast.success('任务创建成功');
|
||
}
|
||
setShowTaskForm(false);
|
||
setEditingTask(null);
|
||
};
|
||
```
|
||
|
||
**评估**: ✅ **完全实现**
|
||
- 完整的表单界面
|
||
- 所有必要字段都已包含
|
||
- 支持创建和编辑
|
||
- 数据持久化到 localStorage
|
||
- 成功提示
|
||
|
||
---
|
||
|
||
### 3️⃣ 任务分配功能 ⚠️
|
||
|
||
**当前实现方式**: 通过表单选择下拉框
|
||
|
||
**TaskForm.tsx 资源分配部分** (第304-360行):
|
||
```tsx
|
||
<Card className="p-4">
|
||
<h3 className="mb-4">资源分配(可稍后分配)</h3>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
{/* 农机选择 */}
|
||
<div>
|
||
<Label>农机设备</Label>
|
||
<Select value={selectedMachinery} onValueChange={setSelectedMachinery}>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="选择农机(可选)" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="unassigned">暂不分配</SelectItem>
|
||
{machinery.filter(m => m.status !== '维修中').map(m => (
|
||
<SelectItem key={m.id} value={m.id}>
|
||
{m.name} - {m.model}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
{/* 驾驶员选择 */}
|
||
<div>
|
||
<Label>驾驶员</Label>
|
||
<Select value={selectedDriver} onValueChange={setSelectedDriver}>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="选择驾驶员(可选)" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="unassigned">暂不分配</SelectItem>
|
||
{drivers.filter(d => d.status === 'active').map(d => (
|
||
<SelectItem key={d.id} value={d.id}>
|
||
{d.name} - {d.phone}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
```
|
||
|
||
**分配逻辑** (TaskAssignment.tsx 第140-160行):
|
||
```tsx
|
||
const handleAssignResources = (taskId: string, machineryId: string | null, driverId: string | null) => {
|
||
const newTasks = tasks.map(t => {
|
||
if (t.id === taskId) {
|
||
const machineryData = machinery.find(m => m.id === machineryId);
|
||
const driverData = drivers.find(d => d.id === driverId);
|
||
return {
|
||
...t,
|
||
machineryId,
|
||
machineryName: machineryData?.name,
|
||
driverId,
|
||
driverName: driverData?.name,
|
||
status: (machineryId && driverId ? '已分配' : '待分配') as Task['status'],
|
||
updatedAt: new Date().toISOString(),
|
||
};
|
||
}
|
||
return t;
|
||
});
|
||
setTasks(newTasks);
|
||
localStorage.setItem('smart_agriculture_tasks', JSON.stringify(newTasks));
|
||
toast.success('资源分配成功');
|
||
};
|
||
```
|
||
|
||
**问题**:
|
||
- ❌ **没有拖拽功能**
|
||
- ❌ 只能通过下拉框选择分配
|
||
- ❌ 不支持从资源面板拖拽到任务
|
||
- ❌ 用户体验不够直观
|
||
|
||
**需求**: 通过**拖拽**等方式分配任务
|
||
|
||
**评估**: ⚠️ **部分实现** - 功能可用,但缺少拖拽交互
|
||
|
||
---
|
||
|
||
### 4️⃣ 优先级设置 ✅
|
||
|
||
**类型定义** (`/types/task.ts` 第60行):
|
||
```tsx
|
||
export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent';
|
||
```
|
||
|
||
**选择界面** (TaskForm.tsx 第188-202行):
|
||
```tsx
|
||
<div>
|
||
<Label>优先级</Label>
|
||
<div className="flex gap-2 mt-2">
|
||
{(['low', 'medium', 'high', 'urgent'] as TaskPriority[]).map(priority => (
|
||
<Badge
|
||
key={priority}
|
||
variant={selectedPriority === priority ? 'default' : 'outline'}
|
||
className={`cursor-pointer ${selectedPriority === priority ? getPriorityColor(priority) : ''}`}
|
||
onClick={() => setSelectedPriority(priority)}
|
||
>
|
||
{getPriorityText(priority)}
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
**颜色编码** (TaskForm.tsx 第101-109行):
|
||
```tsx
|
||
const getPriorityColor = (priority: TaskPriority) => {
|
||
switch (priority) {
|
||
case 'urgent': return 'bg-red-100 text-red-700 border-red-300'; // ✅ 红色 - 紧急
|
||
case 'high': return 'bg-orange-100 text-orange-700 border-orange-300'; // ✅ 橙色 - 高
|
||
case 'medium': return 'bg-yellow-100 text-yellow-700 border-yellow-300'; // ✅ 黄色 - 中
|
||
case 'low': return 'bg-blue-100 text-blue-700 border-blue-300'; // ✅ 蓝色 - 低
|
||
default: return 'bg-gray-100 text-gray-700';
|
||
}
|
||
};
|
||
```
|
||
|
||
**文本映射** (TaskForm.tsx 第111-119行):
|
||
```tsx
|
||
const getPriorityText = (priority: TaskPriority) => {
|
||
switch (priority) {
|
||
case 'urgent': return '紧急';
|
||
case 'high': return '高';
|
||
case 'medium': return '中';
|
||
case 'low': return '低';
|
||
default: return priority;
|
||
}
|
||
};
|
||
```
|
||
|
||
**任务列表显示** (TaskAssignment.tsx 第266-268行):
|
||
```tsx
|
||
<Badge variant="secondary" className={getPriorityColor(task.priority)}>
|
||
{getPriorityText(task.priority)}
|
||
</Badge>
|
||
```
|
||
|
||
**评估**: ✅ **完全实现**
|
||
- 四级优先级(紧急/高/中/低)
|
||
- 清晰的颜色区分
|
||
- 可视化选择界面
|
||
- 在任务列表中醒目显示
|
||
|
||
---
|
||
|
||
### 5️⃣ 冲突检测 ❌
|
||
|
||
**占位函数** (TaskForm.tsx 第121-125行):
|
||
```tsx
|
||
// 检查时间冲突
|
||
const checkConflicts = () => {
|
||
// 这里可以添加时间冲突检测逻辑
|
||
return []; // ❌ 空实现!
|
||
};
|
||
```
|
||
|
||
**UI 准备** (TaskForm.tsx 第458-473行):
|
||
```tsx
|
||
{/* 冲突提醒 */}
|
||
{conflicts.length > 0 && (
|
||
<Card className="p-4 bg-red-50 border-red-200">
|
||
<div className="flex items-start gap-2">
|
||
<AlertCircle className="w-5 h-5 text-red-600 mt-0.5" />
|
||
<div>
|
||
<h4 className="text-red-900 mb-2">检测到冲突</h4>
|
||
<ul className="space-y-1 text-sm text-red-800">
|
||
{conflicts.map((conflict, index) => (
|
||
<li key={index}>• {conflict}</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
)}
|
||
```
|
||
|
||
**问题**:
|
||
- ❌ `checkConflicts` 函数只是占位符
|
||
- ❌ 没有实际的时间冲突检测逻辑
|
||
- ❌ 没有农机重复分配检测
|
||
- ❌ 没有驾驶员重复分配检测
|
||
|
||
**需求**:
|
||
1. 检测同一台农机在同一时段是否被分配多个任务
|
||
2. 检测同一个驾驶员在同一时段是否被分配多个任务
|
||
3. 检测地块在同一时段是否被分配多个任务
|
||
|
||
**评估**: ❌ **未实现** - 有UI界面但缺少核心逻辑
|
||
|
||
---
|
||
|
||
## 🔧 需要完善的功能
|
||
|
||
### 优先级1: 高(核心缺失)
|
||
|
||
#### 1. **实现冲突检测逻辑** 🔴
|
||
|
||
**需要实现的检测**:
|
||
|
||
```tsx
|
||
const checkConflicts = () => {
|
||
const conflicts: string[] = [];
|
||
const startTime = watch('startTime');
|
||
const endTime = watch('endTime');
|
||
|
||
if (!startTime || !endTime) return conflicts;
|
||
|
||
const start = new Date(startTime);
|
||
const end = new Date(endTime);
|
||
|
||
// 1. 检查农机时间冲突
|
||
if (selectedMachinery && selectedMachinery !== 'unassigned') {
|
||
const machineryConflicts = tasks.filter(task => {
|
||
if (editingTask && task.id === editingTask.id) return false;
|
||
if (task.machineryId !== selectedMachinery) return false;
|
||
|
||
const taskStart = new Date(task.startTime);
|
||
const taskEnd = new Date(task.endTime);
|
||
|
||
// 检查时间重叠
|
||
return (start < taskEnd && end > taskStart);
|
||
});
|
||
|
||
if (machineryConflicts.length > 0) {
|
||
const machineryName = machinery.find(m => m.id === selectedMachinery)?.name;
|
||
conflicts.push(
|
||
`农机"${machineryName}"在此时段已有${machineryConflicts.length}个任务`
|
||
);
|
||
}
|
||
}
|
||
|
||
// 2. 检查驾驶员时间冲突
|
||
if (selectedDriver && selectedDriver !== 'unassigned') {
|
||
const driverConflicts = tasks.filter(task => {
|
||
if (editingTask && task.id === editingTask.id) return false;
|
||
if (task.driverId !== selectedDriver) return false;
|
||
|
||
const taskStart = new Date(task.startTime);
|
||
const taskEnd = new Date(task.endTime);
|
||
|
||
return (start < taskEnd && end > taskStart);
|
||
});
|
||
|
||
if (driverConflicts.length > 0) {
|
||
const driverName = drivers.find(d => d.id === selectedDriver)?.name;
|
||
conflicts.push(
|
||
`驾驶员"${driverName}"在此时段已有${driverConflicts.length}个任务`
|
||
);
|
||
}
|
||
}
|
||
|
||
// 3. 检查地块时间冲突
|
||
if (selectedField) {
|
||
const fieldConflicts = tasks.filter(task => {
|
||
if (editingTask && task.id === editingTask.id) return false;
|
||
if (task.fieldId !== selectedField) return false;
|
||
|
||
const taskStart = new Date(task.startTime);
|
||
const taskEnd = new Date(task.endTime);
|
||
|
||
return (start < taskEnd && end > taskStart);
|
||
});
|
||
|
||
if (fieldConflicts.length > 0) {
|
||
const fieldName = fields.find(f => f.id === selectedField)?.name;
|
||
conflicts.push(
|
||
`地块"${fieldName}"在此时段已有${fieldConflicts.length}个作业任务`
|
||
);
|
||
}
|
||
}
|
||
|
||
// 4. 检查时间有效性
|
||
if (start >= end) {
|
||
conflicts.push('结束时间必须晚于开始时间');
|
||
}
|
||
|
||
// 5. 检查时间是否在过去
|
||
if (start < new Date()) {
|
||
conflicts.push('开始时间不能早于当前时间');
|
||
}
|
||
|
||
return conflicts;
|
||
};
|
||
```
|
||
|
||
**依赖项**:
|
||
```tsx
|
||
const conflicts = useMemo(() => {
|
||
return checkConflicts();
|
||
}, [
|
||
watch('startTime'),
|
||
watch('endTime'),
|
||
selectedMachinery,
|
||
selectedDriver,
|
||
selectedField,
|
||
tasks
|
||
]);
|
||
```
|
||
|
||
---
|
||
|
||
#### 2. **添加拖拽分配功能** 🔴
|
||
|
||
**方案**: 使用 `react-dnd` 库
|
||
|
||
**安装**:
|
||
```bash
|
||
npm install react-dnd react-dnd-html5-backend
|
||
```
|
||
|
||
**实现思路**:
|
||
|
||
```tsx
|
||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||
|
||
// 1. 设置 DnD Provider
|
||
export function TaskAssignment() {
|
||
return (
|
||
<DndProvider backend={HTML5Backend}>
|
||
{/* 原有内容 */}
|
||
</DndProvider>
|
||
);
|
||
}
|
||
|
||
// 2. 可拖拽的资源卡片
|
||
function DraggableResource({ type, data }) {
|
||
const [{ isDragging }, drag] = useDrag({
|
||
type: type, // 'MACHINERY' 或 'DRIVER'
|
||
item: { id: data.id, name: data.name, type },
|
||
collect: (monitor) => ({
|
||
isDragging: monitor.isDragging(),
|
||
}),
|
||
});
|
||
|
||
return (
|
||
<div
|
||
ref={drag}
|
||
className={`p-3 border rounded-lg cursor-move ${
|
||
isDragging ? 'opacity-50' : ''
|
||
}`}
|
||
>
|
||
{/* 资源信息 */}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 3. 可放置的任务卡片
|
||
function DroppableTask({ task, onDrop }) {
|
||
const [{ isOver }, drop] = useDrop({
|
||
accept: ['MACHINERY', 'DRIVER'],
|
||
drop: (item) => {
|
||
onDrop(task.id, item);
|
||
},
|
||
collect: (monitor) => ({
|
||
isOver: monitor.isOver(),
|
||
}),
|
||
});
|
||
|
||
return (
|
||
<div
|
||
ref={drop}
|
||
className={`p-4 border rounded-lg ${
|
||
isOver ? 'border-green-500 bg-green-50' : ''
|
||
}`}
|
||
>
|
||
{/* 任务信息 */}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 4. 处理拖拽分配
|
||
const handleDrop = (taskId: string, item: { id: string; type: string }) => {
|
||
if (item.type === 'MACHINERY') {
|
||
handleAssignMachinery(taskId, item.id);
|
||
} else if (item.type === 'DRIVER') {
|
||
handleAssignDriver(taskId, item.id);
|
||
}
|
||
};
|
||
```
|
||
|
||
**优势**:
|
||
- ✅ 更直观的交互体验
|
||
- ✅ 符合用户操作习惯
|
||
- ✅ 视觉反馈清晰
|
||
- ✅ 提高分配效率
|
||
|
||
---
|
||
|
||
### 优先级2: 中(用户体验)
|
||
|
||
#### 3. **任务筛选和排序**
|
||
|
||
```tsx
|
||
const [filterStatus, setFilterStatus] = useState<string>('all');
|
||
const [sortBy, setSortBy] = useState<'priority' | 'startTime'>('priority');
|
||
|
||
const filteredAndSortedTasks = useMemo(() => {
|
||
let filtered = tasks;
|
||
|
||
// 筛选
|
||
if (filterStatus !== 'all') {
|
||
filtered = filtered.filter(t => t.status === filterStatus);
|
||
}
|
||
|
||
// 排序
|
||
return filtered.sort((a, b) => {
|
||
if (sortBy === 'priority') {
|
||
const priorityOrder = { urgent: 0, high: 1, medium: 2, low: 3 };
|
||
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
||
} else {
|
||
return new Date(a.startTime).getTime() - new Date(b.startTime).getTime();
|
||
}
|
||
});
|
||
}, [tasks, filterStatus, sortBy]);
|
||
```
|
||
|
||
---
|
||
|
||
#### 4. **任务时间线视图**
|
||
|
||
使用甘特图展示任务时间安排:
|
||
|
||
```tsx
|
||
import { ScatterChart, Scatter, XAxis, YAxis, Tooltip } from 'recharts';
|
||
|
||
function TaskTimeline({ tasks }) {
|
||
const timelineData = tasks.map(task => ({
|
||
name: task.name,
|
||
start: new Date(task.startTime).getTime(),
|
||
end: new Date(task.endTime).getTime(),
|
||
machinery: task.machineryName,
|
||
}));
|
||
|
||
return (
|
||
<Card className="p-6">
|
||
<h3 className="mb-4">任务时间线</h3>
|
||
<ResponsiveContainer width="100%" height={400}>
|
||
<ScatterChart>
|
||
<XAxis dataKey="start" type="number" domain={['auto', 'auto']} />
|
||
<YAxis dataKey="machinery" type="category" />
|
||
<Tooltip />
|
||
<Scatter data={timelineData} fill="#10b981" />
|
||
</ScatterChart>
|
||
</ResponsiveContainer>
|
||
</Card>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### 5. **批量分配功能**
|
||
|
||
```tsx
|
||
const [selectedTasks, setSelectedTasks] = useState<string[]>([]);
|
||
|
||
const handleBatchAssign = (machineryId: string, driverId: string) => {
|
||
selectedTasks.forEach(taskId => {
|
||
handleAssignResources(taskId, machineryId, driverId);
|
||
});
|
||
setSelectedTasks([]);
|
||
toast.success(`已批量分配${selectedTasks.length}个任务`);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### 优先级3: 低(高级功能)
|
||
|
||
#### 6. **智能推荐**
|
||
|
||
```tsx
|
||
const getRecommendedMachinery = (task: Task) => {
|
||
// 根据任务类型、地块面积、优先级推荐合适的农机
|
||
return machinery
|
||
.filter(m => m.status !== '维修中')
|
||
.filter(m => !hasConflict(m.id, task))
|
||
.sort((a, b) => {
|
||
// 按照适配度排序
|
||
return calculateScore(b, task) - calculateScore(a, task);
|
||
});
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
#### 7. **任务模板**
|
||
|
||
```tsx
|
||
const saveTaskTemplate = (task: Task) => {
|
||
const template = {
|
||
name: task.name,
|
||
taskType: task.taskType,
|
||
requirements: task.requirements,
|
||
};
|
||
|
||
const templates = JSON.parse(localStorage.getItem('task_templates') || '[]');
|
||
templates.push(template);
|
||
localStorage.setItem('task_templates', JSON.stringify(templates));
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ 功能完善清单
|
||
|
||
### 核心功能(必须)
|
||
|
||
- [x] ✅ 查看农机状态(空闲/作业中/维修中)
|
||
- [x] ✅ 查看驾驶员状态
|
||
- [x] ✅ 创建任务
|
||
- [x] ✅ 设置任务优先级(4级)
|
||
- [ ] ❌ 通过拖拽分配任务
|
||
- [ ] ❌ 冲突检测逻辑实现
|
||
|
||
### 增强功能(推荐)
|
||
|
||
- [ ] ❌ 任务筛选和排序
|
||
- [ ] ❌ 任务时间线视图
|
||
- [ ] ❌ 批量分配功能
|
||
- [ ] ❌ 任务编辑和删除优化
|
||
- [ ] ❌ 任务状态流转
|
||
|
||
### 高级功能(可选)
|
||
|
||
- [ ] ❌ 智能推荐农机/驾驶员
|
||
- [ ] ❌ 任务模板管理
|
||
- [ ] ❌ 任务统计分析
|
||
- [ ] ❌ 导出任务报表
|
||
|
||
---
|
||
|
||
## 📊 评分总结
|
||
|
||
| 功能模块 | 评分 | 完成度 |
|
||
|---------|------|--------|
|
||
| 状态查看 | ⭐⭐⭐⭐⭐ | 100% |
|
||
| 任务创建 | ⭐⭐⭐⭐⭐ | 100% |
|
||
| 优先级设置 | ⭐⭐⭐⭐⭐ | 100% |
|
||
| 任务分配 | ⭐⭐⭐☆☆ | 60% |
|
||
| 冲突检测 | ⭐☆☆☆☆ | 20% |
|
||
| **总体评分** | **⭐⭐⭐☆☆** | **76%** |
|
||
|
||
---
|
||
|
||
## 🎯 实施建议
|
||
|
||
### Phase 1: 补全核心功能(优先级最高)
|
||
|
||
**任务1**: 实现冲突检测逻辑(2-3小时)
|
||
- 检测农机时间冲突
|
||
- 检测驾驶员时间冲突
|
||
- 检测地块时间冲突
|
||
- 时间有效性检查
|
||
|
||
**任务2**: 添加拖拽分配功能(4-5小时)
|
||
- 安装 react-dnd
|
||
- 实现可拖拽资源卡片
|
||
- 实现可放置任务卡片
|
||
- 优化拖拽交互体验
|
||
|
||
---
|
||
|
||
### Phase 2: 增强用户体验(推荐实现)
|
||
|
||
**任务3**: 任务筛选和排序(1-2小时)
|
||
- 按状态筛选
|
||
- 按优先级排序
|
||
- 按时间排序
|
||
|
||
**任务4**: 任务时间线视图(2-3小时)
|
||
- 实现甘特图展示
|
||
- 显示任务时间段
|
||
- 标注冲突任务
|
||
|
||
---
|
||
|
||
### Phase 3: 高级功能(可选)
|
||
|
||
**任务5**: 智能推荐(3-4小时)
|
||
**任务6**: 批量操作(1-2小时)
|
||
**任务7**: 任务模板(2-3小时)
|
||
|
||
---
|
||
|
||
## 📝 代码示例:完整的冲突检测
|
||
|
||
### TaskForm.tsx 更新
|
||
|
||
```tsx
|
||
import { useState, useMemo } from 'react';
|
||
// ... 其他导入
|
||
|
||
export function TaskForm({
|
||
open,
|
||
onClose,
|
||
onSave,
|
||
editingTask,
|
||
machinery,
|
||
drivers,
|
||
fields,
|
||
existingTasks // ⬅️ 新增:需要传入现有任务列表
|
||
}: TaskFormProps) {
|
||
|
||
// ... 现有状态
|
||
|
||
// ✅ 实现冲突检测
|
||
const conflicts = useMemo(() => {
|
||
const conflictList: string[] = [];
|
||
const startTimeValue = watch('startTime');
|
||
const endTimeValue = watch('endTime');
|
||
|
||
if (!startTimeValue || !endTimeValue) {
|
||
return conflictList;
|
||
}
|
||
|
||
const startDate = new Date(startTimeValue);
|
||
const endDate = new Date(endTimeValue);
|
||
|
||
// 1. 时间有效性检查
|
||
if (startDate >= endDate) {
|
||
conflictList.push('⚠️ 结束时间必须晚于开始时间');
|
||
}
|
||
|
||
if (startDate < new Date()) {
|
||
conflictList.push('⚠️ 开始时间不能早于当前时间');
|
||
}
|
||
|
||
// 2. 农机冲突检测
|
||
if (selectedMachinery && selectedMachinery !== 'unassigned') {
|
||
const machineryTasks = existingTasks.filter(task => {
|
||
// 排除正在编辑的任务
|
||
if (editingTask && task.id === editingTask.id) return false;
|
||
// 只检查已分配给该农机的任务
|
||
if (task.machineryId !== selectedMachinery) return false;
|
||
|
||
const taskStart = new Date(task.startTime);
|
||
const taskEnd = new Date(task.endTime);
|
||
|
||
// 检查时间重叠
|
||
return (startDate < taskEnd && endDate > taskStart);
|
||
});
|
||
|
||
if (machineryTasks.length > 0) {
|
||
const machineryData = machinery.find(m => m.id === selectedMachinery);
|
||
conflictList.push(
|
||
`🚜 农机"${machineryData?.name}"在此时段已有 ${machineryTasks.length} 个任务:${
|
||
machineryTasks.map(t => t.name).join('、')
|
||
}`
|
||
);
|
||
}
|
||
}
|
||
|
||
// 3. 驾驶员冲突检测
|
||
if (selectedDriver && selectedDriver !== 'unassigned') {
|
||
const driverTasks = existingTasks.filter(task => {
|
||
if (editingTask && task.id === editingTask.id) return false;
|
||
if (task.driverId !== selectedDriver) return false;
|
||
|
||
const taskStart = new Date(task.startTime);
|
||
const taskEnd = new Date(task.endTime);
|
||
|
||
return (startDate < taskEnd && endDate > taskStart);
|
||
});
|
||
|
||
if (driverTasks.length > 0) {
|
||
const driverData = drivers.find(d => d.id === selectedDriver);
|
||
conflictList.push(
|
||
`👤 驾驶员"${driverData?.name}"在此时段已有 ${driverTasks.length} 个任务:${
|
||
driverTasks.map(t => t.name).join('、')
|
||
}`
|
||
);
|
||
}
|
||
}
|
||
|
||
// 4. 地块冲突检测
|
||
if (selectedField) {
|
||
const fieldTasks = existingTasks.filter(task => {
|
||
if (editingTask && task.id === editingTask.id) return false;
|
||
if (task.fieldId !== selectedField) return false;
|
||
|
||
const taskStart = new Date(task.startTime);
|
||
const taskEnd = new Date(task.endTime);
|
||
|
||
return (startDate < taskEnd && endDate > taskStart);
|
||
});
|
||
|
||
if (fieldTasks.length > 0) {
|
||
const fieldData = fields.find(f => f.id === selectedField);
|
||
conflictList.push(
|
||
`🌾 地块"${fieldData?.name}"在此时段已有 ${fieldTasks.length} 个作业任务:${
|
||
fieldTasks.map(t => t.name).join('、')
|
||
}`
|
||
);
|
||
}
|
||
}
|
||
|
||
return conflictList;
|
||
}, [
|
||
watch('startTime'),
|
||
watch('endTime'),
|
||
selectedMachinery,
|
||
selectedDriver,
|
||
selectedField,
|
||
existingTasks,
|
||
editingTask,
|
||
machinery,
|
||
drivers,
|
||
fields
|
||
]);
|
||
|
||
// ... 其余代码
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={onClose}>
|
||
{/* ... */}
|
||
|
||
{/* ✅ 冲突提醒(现在会实际显示) */}
|
||
{conflicts.length > 0 && (
|
||
<Card className="p-4 bg-red-50 border-red-200">
|
||
<div className="flex items-start gap-2">
|
||
<AlertCircle className="w-5 h-5 text-red-600 mt-0.5 flex-shrink-0" />
|
||
<div className="flex-1">
|
||
<h4 className="text-red-900 mb-2">⚠️ 检测到 {conflicts.length} 个冲突</h4>
|
||
<ul className="space-y-1 text-sm text-red-800">
|
||
{conflicts.map((conflict, index) => (
|
||
<li key={index} className="flex items-start gap-1">
|
||
<span className="mt-1">•</span>
|
||
<span className="flex-1">{conflict}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
<p className="text-xs text-red-700 mt-2">
|
||
提示:您仍然可以保存此任务,但建议先解决冲突
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
)}
|
||
|
||
{/* ... */}
|
||
</Dialog>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### TaskAssignment.tsx 更新
|
||
|
||
```tsx
|
||
{/* 传入现有任务列表 */}
|
||
<TaskForm
|
||
open={showTaskForm}
|
||
onClose={() => {
|
||
setShowTaskForm(false);
|
||
setEditingTask(null);
|
||
}}
|
||
onSave={handleSaveTask}
|
||
editingTask={editingTask}
|
||
machinery={machinery}
|
||
drivers={drivers}
|
||
fields={fields}
|
||
existingTasks={tasks} // ⬅️ 新增
|
||
/>
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 冲突检测效果预览
|
||
|
||
### 无冲突
|
||
```
|
||
✅ 所有检查通过,可以创建任务
|
||
```
|
||
|
||
### 有冲突
|
||
```
|
||
┌────────────────────────────────────────────────────┐
|
||
│ ⚠️ 检测到 3 个冲突 │
|
||
├────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ • 🚜 农机"约翰迪尔拖拉机"在此时段已有 1 个任务: │
|
||
│ 1号地块耕地作业 │
|
||
│ │
|
||
│ • 👤 驾驶员"张三"在此时段已有 1 个任务: │
|
||
│ 2号地块播种 │
|
||
│ │
|
||
│ • 🌾 地块"1号地块"在此时段已有 1 个作业任务: │
|
||
│ 1号地块耕地作业 │
|
||
│ │
|
||
│ 提示:您仍然可以保存此任务,但建议先解决冲突 │
|
||
└────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 📖 总结
|
||
|
||
### 当前状态
|
||
|
||
**已实现** ✅:
|
||
1. 完整的状态查看功能
|
||
2. 完整的任务创建功能
|
||
3. 完整的优先级设置
|
||
4. 基础的资源分配功能
|
||
|
||
**未实现** ❌:
|
||
1. 拖拽分配功能
|
||
2. 冲突检测逻辑
|
||
|
||
**评价**:
|
||
基础框架完整,核心功能可用,但缺少两个关键功能:
|
||
- **拖拽交互** - 影响用户体验
|
||
- **冲突检测** - 影响系统可靠性
|
||
|
||
---
|
||
|
||
### 建议
|
||
|
||
**立即实现** 🔴:
|
||
1. ✅ 冲突检测逻辑(2-3小时)
|
||
2. ✅ 拖拽分配功能(4-5小时)
|
||
|
||
**预计工作量**: 6-8小时完成核心缺失功能
|
||
|
||
**完成后评分**: ⭐⭐⭐⭐⭐ (100%)
|
||
|
||
---
|
||
|
||
**文档版本**: v1.0
|
||
**检查日期**: 2025-10-17
|
||
**状态**: ⚠️ **需要补全核心功能(冲突检测、拖拽)**
|
||
|
||
**总体评价**: 功能基本完善,但缺少需求明确提到的"拖拽"和"冲突检测"两个核心功能。
|