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 { StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { Input, BottomSheet, ListItem } from '@rneui/themed'; import { Input, BottomSheet, ListItem } from '@rneui/themed';
import { Task, RobotAction } from '../types/task'; import { Task, RobotAction } from '../types/task';
import { import { LOCATIONS, ROBOT_ACTIONS, PAYLOADS } from '../data/mockData';
LOCATIONS,
ROBOT_ACTIONS,
PAYLOADS,
LOCATIONS_BAYS,
} from '../data/mockData';
interface TaskFormProps { interface TaskFormProps {
task: Task; task: Task;
onTaskChange: (updatedTask: Task) => void; 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 [isVisible, setIsVisible] = useState(false);
const [currentField, setCurrentField] = useState(''); const [currentField, setCurrentField] = useState('');
const [currentItems, setCurrentItems] = useState< const [currentItems, setCurrentItems] = useState<
{ label: string; value: string }[] { 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 handleParamChange = (field: string, value: string | RobotAction) => {
const updatedTask = { const updatedTask = {
...task, ...task,
@ -91,12 +145,17 @@ const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
ROBOT_ACTIONS, ROBOT_ACTIONS,
)} )}
{renderDropdown('payload', '载荷', task.parameters.payload, PAYLOADS)} {renderDropdown('payload', '载荷', task.parameters.payload, PAYLOADS)}
{renderDropdown(
'locationBay', {/* 库位字段改为普通输入框 */}
'库位', <Input
task.parameters.locationBay || '', ref={locationBayInputRef}
LOCATIONS_BAYS, label="库位"
)} value={task.parameters.locationBay || ''}
onChangeText={text => handleParamChange('locationBay', text)}
showSoftInputOnFocus={false}
autoFocus={false}
placeholder="请输入库位"
/>
<BottomSheet <BottomSheet
isVisible={isVisible} isVisible={isVisible}

View File

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

View File

@ -32,7 +32,6 @@ export default function TaskEditScreen() {
} }
}, [initialTask]); }, [initialTask]);
const handleTaskChange = (updatedTask: Task) => { const handleTaskChange = (updatedTask: Task) => {
setTask(updatedTask); setTask(updatedTask);
if (!isModified) { if (!isModified) {
@ -48,35 +47,48 @@ export default function TaskEditScreen() {
Alert.alert('已保存', `任务 "${task.name}" 已被保存。`); Alert.alert('已保存', `任务 "${task.name}" 已被保存。`);
} }
}; };
const handleRun = () => { const handleRun = () => {
if(task) { if (task) {
runTask(task.id); runTask(task.id);
navigation.goBack(); navigation.goBack();
} }
} };
const handleUndo = () => { const handleUndo = () => {
setTask(originalTask); setTask(originalTask);
setIsModified(false); setIsModified(false);
} };
const handleRestore = () => { const handleRestore = () => {
setTask(originalTask); setTask(originalTask);
setIsModified(false); setIsModified(false);
} };
// 处理库位变化,自动运行任务
const handleLocationBayChange = (updatedTask: Task) => {
console.log(
'库位已变化,自动运行任务:',
updatedTask.parameters.locationBay,
);
runTask(updatedTask.id);
};
if (!task) { if (!task) {
return ( return (
<Dialog isVisible={true}> <Dialog isVisible={true}>
<Dialog.Loading /> <Dialog.Loading />
</Dialog> </Dialog>
); );
} }
return ( return (
<View style={styles.container}> <View style={styles.container}>
<TaskForm task={task} onTaskChange={handleTaskChange} /> <TaskForm
task={task}
onTaskChange={handleTaskChange}
onLocationBayChange={handleLocationBayChange}
/>
<BottomActionBar <BottomActionBar
onRun={handleRun} onRun={handleRun}
onSave={handleSave} 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'; export type TaskStatus = 'IDLE' | 'RUNNING' | 'COMPLETED' | 'ERROR';
@ -7,11 +13,11 @@ export type TaskStatus = 'IDLE' | 'RUNNING' | 'COMPLETED' | 'ERROR';
// 任务参数 // 任务参数
export interface TaskParameters { export interface TaskParameters {
startLocation: string; // 起点 startLocation: string; // 起点
endLocation: string; // 终点 endLocation: string; // 终点
waypoint?: string; // 途经点 (可选) waypoint?: string; // 途经点 (可选)
robotAction: RobotAction; // 机器人动作 robotAction: RobotAction; // 机器人动作
payload: string; // 载荷,比如 '空料架' 或具体的物料ID payload: string; // 载荷,比如 '空料架' 或具体的物料ID
locationBay?: string; // 库位 (可选) locationBay?: string; // 库位 (可选)
} }
// 核心任务对象 // 核心任务对象