feat: 添加库位输入框及变化监听,自动运行任务

This commit is contained in:
xudan 2025-07-22 11:16:39 +08:00
parent 02a326e60e
commit 726b7db95f
4 changed files with 148 additions and 61 deletions

View File

@ -1,26 +1,80 @@
import React, { useState } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { Input, BottomSheet, ListItem } from '@rneui/themed';
import { Task, RobotAction } from '../types/task';
import {
LOCATIONS,
ROBOT_ACTIONS,
PAYLOADS,
LOCATIONS_BAYS,
} from '../data/mockData';
import { LOCATIONS, ROBOT_ACTIONS, PAYLOADS } from '../data/mockData';
interface TaskFormProps {
task: Task;
onTaskChange: (updatedTask: Task) => void;
onLocationBayChange?: (task: Task) => void; // 库位变化时的回调
}
const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
const TaskForm: React.FC<TaskFormProps> = ({
task,
onTaskChange,
onLocationBayChange,
}) => {
const [isVisible, setIsVisible] = useState(false);
const [currentField, setCurrentField] = useState('');
const [currentItems, setCurrentItems] = useState<
{ label: string; value: string }[]
>([]);
// 创建库位输入框的ref
const locationBayInputRef = useRef<any>(null);
// 用于防抖的ref
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
// 保存上一次的库位值,用于检测变化
const previousLocationBayRef = useRef<string>(
task.parameters.locationBay || '',
);
// 组件挂载时自动聚焦到库位输入框
useEffect(() => {
const timer = setTimeout(() => {
if (locationBayInputRef.current) {
locationBayInputRef.current.focus();
}
}, 100);
return () => clearTimeout(timer);
}, []);
// 监听库位值变化,自动触发运行任务
useEffect(() => {
const currentLocationBay = task.parameters.locationBay || '';
const previousLocationBay = previousLocationBayRef.current;
// 只有当库位值真正发生变化且不为空时才触发
if (
currentLocationBay !== previousLocationBay &&
currentLocationBay.trim() !== ''
) {
// 清除之前的防抖定时器
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
// 设置新的防抖定时器500ms后触发
debounceTimerRef.current = setTimeout(() => {
if (onLocationBayChange) {
onLocationBayChange(task);
}
}, 500);
}
// 更新上一次的库位值
previousLocationBayRef.current = currentLocationBay;
// 清理函数
return () => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
};
}, [task.parameters.locationBay, task, onLocationBayChange]);
const handleParamChange = (field: string, value: string | RobotAction) => {
const updatedTask = {
...task,
@ -91,12 +145,17 @@ const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
ROBOT_ACTIONS,
)}
{renderDropdown('payload', '载荷', task.parameters.payload, PAYLOADS)}
{renderDropdown(
'locationBay',
'库位',
task.parameters.locationBay || '',
LOCATIONS_BAYS,
)}
{/* 库位字段改为普通输入框 */}
<Input
ref={locationBayInputRef}
label="库位"
value={task.parameters.locationBay || ''}
onChangeText={text => handleParamChange('locationBay', text)}
showSoftInputOnFocus={false}
autoFocus={false}
placeholder="请输入库位"
/>
<BottomSheet
isVisible={isVisible}

View File

@ -2,8 +2,8 @@ import { Task, RobotAction } from '../types/task';
import { v4 as uuidv4 } from 'uuid';
export const LOCATIONS = Array.from({ length: 100 }, (_, i) => ({
label: `AP-${i + 1}`,
value: `AP-${i + 1}`,
label: `AP-${i + 1}`,
value: `AP-${i + 1}`,
}));
export const ROBOT_ACTIONS: { label: string; value: RobotAction }[] = [
@ -16,38 +16,48 @@ export const ROBOT_ACTIONS: { label: string; value: RobotAction }[] = [
];
export const LOCATIONS_BAYS = Array.from({ length: 100 }, (_, i) => ({
label: `AS2_2_${String(i + 1).padStart(3, '0')}`,
value: `AS2_2_${String(i + 1).padStart(3, '0')}`,
label: `AS2_2_${String(i + 1).padStart(3, '0')}`,
value: `AS2_2_${String(i + 1).padStart(3, '0')}`,
}));
export const PAYLOADS = [
{ label: '满料架-A1', value: '满料架-A1' },
{ label: '空料架-B2', value: '空料架-B2' },
{ label: '空料架-C3', value: '空料架-C3' },
{ label: '空车', value: '空车' },
{ label: '满料架-A1', value: '满料架-A1' },
{ label: '空料架-B2', value: '空料架-B2' },
{ label: '空料架-C3', value: '空料架-C3' },
{ label: '空车', value: '空车' },
];
const getRandomElement = <T>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)];
const getRandomElement = <T>(arr: T[]): T =>
arr[Math.floor(Math.random() * arr.length)];
export const MOCK_TASKS: Task[] = Array.from({ length: 15 }, (_, i) => {
const startLocation = getRandomElement(LOCATIONS);
const endLocation = getRandomElement(LOCATIONS.filter(l => l.value !== startLocation.value));
const robotAction = getRandomElement(ROBOT_ACTIONS);
const payload = getRandomElement(PAYLOADS);
const statusOptions: Task['status'][] = ['IDLE', 'RUNNING', 'COMPLETED', 'ERROR'];
const status = getRandomElement(statusOptions);
const startLocation = getRandomElement(LOCATIONS);
const endLocation = getRandomElement(
LOCATIONS.filter(l => l.value !== startLocation.value),
);
const robotAction = getRandomElement(ROBOT_ACTIONS);
const payload = getRandomElement(PAYLOADS);
const statusOptions: Task['status'][] = [
'IDLE',
'RUNNING',
'COMPLETED',
'ERROR',
];
const status = getRandomElement(statusOptions);
return {
id: uuidv4(),
name: `任务 ${i + 1}: 从 ${startLocation.label}${endLocation.label}`,
status: status,
createdAt: new Date(new Date().getTime() - Math.random() * 1000 * 60 * 60 * 24).toISOString(),
parameters: {
startLocation: startLocation.value,
endLocation: endLocation.value,
robotAction: robotAction.value,
payload: payload.value,
locationBay: getRandomElement(LOCATIONS_BAYS).value,
},
};
return {
id: uuidv4(),
name: `任务 ${i + 1}: 从 ${startLocation.label}${endLocation.label}`,
status: status,
createdAt: new Date(
new Date().getTime() - Math.random() * 1000 * 60 * 60 * 24,
).toISOString(),
parameters: {
startLocation: startLocation.value,
endLocation: endLocation.value,
robotAction: robotAction.value,
payload: payload.value,
locationBay: '', // 库位初始值为空
},
};
});

View File

@ -32,7 +32,6 @@ export default function TaskEditScreen() {
}
}, [initialTask]);
const handleTaskChange = (updatedTask: Task) => {
setTask(updatedTask);
if (!isModified) {
@ -48,35 +47,48 @@ export default function TaskEditScreen() {
Alert.alert('已保存', `任务 "${task.name}" 已被保存。`);
}
};
const handleRun = () => {
if(task) {
runTask(task.id);
navigation.goBack();
if (task) {
runTask(task.id);
navigation.goBack();
}
}
};
const handleUndo = () => {
setTask(originalTask);
setIsModified(false);
}
setTask(originalTask);
setIsModified(false);
};
const handleRestore = () => {
setTask(originalTask);
setIsModified(false);
}
};
// 处理库位变化,自动运行任务
const handleLocationBayChange = (updatedTask: Task) => {
console.log(
'库位已变化,自动运行任务:',
updatedTask.parameters.locationBay,
);
runTask(updatedTask.id);
};
if (!task) {
return (
<Dialog isVisible={true}>
<Dialog.Loading />
</Dialog>
<Dialog isVisible={true}>
<Dialog.Loading />
</Dialog>
);
}
return (
<View style={styles.container}>
<TaskForm task={task} onTaskChange={handleTaskChange} />
<TaskForm
task={task}
onTaskChange={handleTaskChange}
onLocationBayChange={handleLocationBayChange}
/>
<BottomActionBar
onRun={handleRun}
onSave={handleSave}

View File

@ -1,5 +1,11 @@
// 机器人的具体动作,可以定义为枚举或联合类型
export type RobotAction = 'PICKUP' | 'DROPOFF' | 'TRANSPORT' | 'WAIT';
export type RobotAction =
| 'PICKUP'
| 'DROPOFF'
| 'TRANSPORT'
| 'WAIT'
| 'CHARGE'
| 'CLEAN';
// 任务的状态
export type TaskStatus = 'IDLE' | 'RUNNING' | 'COMPLETED' | 'ERROR';
@ -7,11 +13,11 @@ export type TaskStatus = 'IDLE' | 'RUNNING' | 'COMPLETED' | 'ERROR';
// 任务参数
export interface TaskParameters {
startLocation: string; // 起点
endLocation: string; // 终点
waypoint?: string; // 途经点 (可选)
endLocation: string; // 终点
waypoint?: string; // 途经点 (可选)
robotAction: RobotAction; // 机器人动作
payload: string; // 载荷,比如 '空料架' 或具体的物料ID
locationBay?: string; // 库位 (可选)
payload: string; // 载荷,比如 '空料架' 或具体的物料ID
locationBay?: string; // 库位 (可选)
}
// 核心任务对象