webapp/src/components/TaskForm.tsx

319 lines
8.2 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import { StyleSheet, ScrollView, TouchableOpacity, View } from 'react-native';
import { Input, BottomSheet, ListItem, Button } from '@rneui/themed';
import { Task, RobotAction } from '../types/task';
import { useTasks } from '../context/TasksContext';
interface TaskFormProps {
task: Task;
onTaskChange: (updatedTask: Task) => void;
}
const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
const { locations, payloads, robotActions } = useTasks();
const [isVisible, setIsVisible] = useState(false);
const [currentField, setCurrentField] = useState('');
const [currentItems, setCurrentItems] = useState<
{ label: string; value: string }[]
>([]);
const [isQrInputFocused, setIsQrInputFocused] = useState(true);
// 创建隐藏的二维码扫描输入框的ref
const qrScanInputRef = useRef<any>(null);
// 用于防止循环更新的标志
const isUpdatingFromQrRef = useRef(false);
const isUpdatingFromFormRef = useRef(false);
// 组件挂载时自动聚焦到隐藏的扫描输入框
useEffect(() => {
const timer = setTimeout(() => {
if (qrScanInputRef.current) {
qrScanInputRef.current.focus();
}
}, 100);
return () => clearTimeout(timer);
}, []);
// 解析二维码信息对象字面量格式
const parseQrCodeInfo = (infoString: string): Partial<Task['parameters']> => {
const result: Partial<Task['parameters']> = {};
try {
// 移除花括号和换行符,准备解析
let cleanString = infoString.trim();
if (cleanString.startsWith('{')) {
cleanString = cleanString.substring(1);
}
if (cleanString.endsWith('}')) {
cleanString = cleanString.substring(0, cleanString.length - 1);
}
// 按逗号分割各个属性
const items = cleanString.split(',');
for (const item of items) {
const trimmedItem = item.trim();
if (!trimmedItem) continue;
// 解析 "key: value" 格式
const colonIndex = trimmedItem.indexOf(':');
if (colonIndex === -1) continue;
const key = trimmedItem.substring(0, colonIndex).trim();
const value = trimmedItem.substring(colonIndex + 1).trim();
// 映射字段名
switch (key) {
case 'startLocation':
result.startLocation = value;
break;
case 'endLocation':
result.endLocation = value;
break;
case 'waypoint':
result.waypoint = value;
break;
case 'robotAction':
result.robotAction = value as RobotAction;
break;
case 'payload':
result.payload = value;
break;
case 'locationBay':
result.locationBay = value;
break;
}
}
} catch (error) {
// 解析失败时不报错,返回空对象
console.log('解析二维码信息失败:', error);
}
return result;
};
// 处理二维码扫描输入,更新表单字段
const handleQrCodeScan = (scanData: string) => {
if (isUpdatingFromFormRef.current || !scanData.trim()) return;
isUpdatingFromQrRef.current = true;
const parsedParams = parseQrCodeInfo(scanData);
const updatedTask = {
...task,
parameters: {
...task.parameters,
...parsedParams,
},
};
onTaskChange(updatedTask);
// 清空扫描输入框
if (qrScanInputRef.current) {
qrScanInputRef.current.clear();
}
// 重置标志
setTimeout(() => {
isUpdatingFromQrRef.current = false;
}, 0);
};
// 重新扫描按钮点击处理
const handleRescan = () => {
if (qrScanInputRef.current) {
qrScanInputRef.current.focus();
}
};
// 处理其他输入框获得焦点
const handleOtherInputFocus = () => {
setIsQrInputFocused(false);
};
// 处理二维码输入框焦点事件
const handleQrInputFocus = () => {
setIsQrInputFocused(true);
};
const handleQrInputBlur = () => {
setIsQrInputFocused(false);
};
const handleParamChange = (field: string, value: string | RobotAction) => {
if (isUpdatingFromQrRef.current) return;
isUpdatingFromFormRef.current = true;
const updatedTask = {
...task,
parameters: {
...task.parameters,
[field]: value,
},
};
onTaskChange(updatedTask);
// 重置标志
setTimeout(() => {
isUpdatingFromFormRef.current = false;
}, 0);
};
const openBottomSheet = (
field: string,
items: { label: string; value: string }[],
) => {
setCurrentField(field);
setCurrentItems(items);
setIsVisible(true);
};
const renderDropdown = (
name: string,
label: string,
value: string,
items: { label: string; value: string }[],
) => (
<TouchableOpacity onPress={() => openBottomSheet(name, items)}>
<Input
label={label}
value={value}
editable={false}
rightIcon={{ name: 'arrow-drop-down' }}
/>
</TouchableOpacity>
);
return (
<ScrollView contentContainerStyle={styles.container}>
{/* 隐藏的二维码扫描输入框 */}
<Input
ref={qrScanInputRef}
style={styles.hiddenInput}
containerStyle={styles.hiddenContainer}
inputContainerStyle={styles.hiddenContainer}
onChangeText={handleQrCodeScan}
onFocus={handleQrInputFocus}
onBlur={handleQrInputBlur}
autoFocus={false}
showSoftInputOnFocus={false}
/>
{/* 扫描二维码按钮 */}
<View style={styles.scanButtonContainer}>
<Button
title={
isQrInputFocused ? '正在等待二维码扫描...' : '扫描二维码填充表单'
}
onPress={handleRescan}
icon={{
name: isQrInputFocused ? 'hourglass-empty' : 'qr-code-scanner',
type: 'material',
}}
buttonStyle={[
styles.scanButton,
isQrInputFocused && styles.waitingButton,
]}
disabled={isQrInputFocused}
/>
</View>
<Input
label="任务名称"
value={task.name}
onChangeText={text => onTaskChange({ ...task, name: text })}
onFocus={handleOtherInputFocus}
/>
{renderDropdown(
'startLocation',
'起点',
task.parameters.startLocation,
locations,
)}
{renderDropdown(
'endLocation',
'终点',
task.parameters.endLocation,
locations,
)}
<Input
label="途经点 (可选)"
value={task.parameters.waypoint || ''}
onChangeText={text => handleParamChange('waypoint', text)}
onFocus={handleOtherInputFocus}
/>
{renderDropdown(
'robotAction',
'机器人动作',
task.parameters.robotAction,
robotActions,
)}
{renderDropdown('payload', '载荷', task.parameters.payload, payloads)}
{/* 库位字段 */}
<Input
label="库位"
value={task.parameters.locationBay || ''}
onChangeText={text => handleParamChange('locationBay', text)}
placeholder="请输入库位"
onFocus={handleOtherInputFocus}
/>
<BottomSheet
isVisible={isVisible}
onBackdropPress={() => setIsVisible(false)}
>
<ScrollView>
{currentItems.map((item, index) => (
<ListItem
key={index}
onPress={() => {
handleParamChange(currentField, item.value);
setIsVisible(false);
}}
>
<ListItem.Content>
<ListItem.Title>{item.label}</ListItem.Title>
</ListItem.Content>
</ListItem>
))}
</ScrollView>
</BottomSheet>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
padding: 16,
},
hiddenInput: {
height: 0,
opacity: 0,
},
hiddenContainer: {
height: 0,
margin: 0,
padding: 0,
},
scanButtonContainer: {
marginBottom: 16,
},
scanButton: {
backgroundColor: '#2196F3',
borderRadius: 8,
},
waitingButton: {
backgroundColor: '#9E9E9E', // 浅灰色,表示等待状态
},
});
export default TaskForm;