feat: 添加二维码扫描功能,优化任务表单输入体验
This commit is contained in:
parent
726b7db95f
commit
0e0cef6eba
@ -1,81 +1,152 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
|
||||
import { Input, BottomSheet, ListItem } from '@rneui/themed';
|
||||
import { StyleSheet, ScrollView, TouchableOpacity, View } from 'react-native';
|
||||
import { Input, BottomSheet, ListItem, Button } from '@rneui/themed';
|
||||
import { Task, RobotAction } from '../types/task';
|
||||
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,
|
||||
onLocationBayChange,
|
||||
}) => {
|
||||
const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [currentField, setCurrentField] = useState('');
|
||||
const [currentItems, setCurrentItems] = useState<
|
||||
{ label: string; value: string }[]
|
||||
>([]);
|
||||
const [isQrInputFocused, setIsQrInputFocused] = useState(true);
|
||||
|
||||
// 创建库位输入框的ref
|
||||
const locationBayInputRef = useRef<any>(null);
|
||||
// 用于防抖的ref
|
||||
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
// 保存上一次的库位值,用于检测变化
|
||||
const previousLocationBayRef = useRef<string>(
|
||||
task.parameters.locationBay || '',
|
||||
);
|
||||
// 创建隐藏的二维码扫描输入框的ref
|
||||
const qrScanInputRef = useRef<any>(null);
|
||||
|
||||
// 组件挂载时自动聚焦到库位输入框
|
||||
// 用于防止循环更新的标志
|
||||
const isUpdatingFromQrRef = useRef(false);
|
||||
const isUpdatingFromFormRef = useRef(false);
|
||||
|
||||
// 组件挂载时自动聚焦到隐藏的扫描输入框
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (locationBayInputRef.current) {
|
||||
locationBayInputRef.current.focus();
|
||||
if (qrScanInputRef.current) {
|
||||
qrScanInputRef.current.focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// 监听库位值变化,自动触发运行任务
|
||||
useEffect(() => {
|
||||
const currentLocationBay = task.parameters.locationBay || '';
|
||||
const previousLocationBay = previousLocationBayRef.current;
|
||||
// 解析二维码信息对象字面量格式
|
||||
const parseQrCodeInfo = (infoString: string): Partial<Task['parameters']> => {
|
||||
const result: Partial<Task['parameters']> = {};
|
||||
|
||||
// 只有当库位值真正发生变化且不为空时才触发
|
||||
if (
|
||||
currentLocationBay !== previousLocationBay &&
|
||||
currentLocationBay.trim() !== ''
|
||||
) {
|
||||
// 清除之前的防抖定时器
|
||||
if (debounceTimerRef.current) {
|
||||
clearTimeout(debounceTimerRef.current);
|
||||
try {
|
||||
// 移除花括号和换行符,准备解析
|
||||
let cleanString = infoString.trim();
|
||||
if (cleanString.startsWith('{')) {
|
||||
cleanString = cleanString.substring(1);
|
||||
}
|
||||
if (cleanString.endsWith('}')) {
|
||||
cleanString = cleanString.substring(0, cleanString.length - 1);
|
||||
}
|
||||
|
||||
// 设置新的防抖定时器,500ms后触发
|
||||
debounceTimerRef.current = setTimeout(() => {
|
||||
if (onLocationBayChange) {
|
||||
onLocationBayChange(task);
|
||||
// 按逗号分割各个属性
|
||||
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;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
} catch (error) {
|
||||
// 解析失败时不报错,返回空对象
|
||||
console.log('解析二维码信息失败:', error);
|
||||
}
|
||||
|
||||
// 更新上一次的库位值
|
||||
previousLocationBayRef.current = currentLocationBay;
|
||||
return result;
|
||||
};
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
if (debounceTimerRef.current) {
|
||||
clearTimeout(debounceTimerRef.current);
|
||||
}
|
||||
// 处理二维码扫描输入,更新表单字段
|
||||
const handleQrCodeScan = (scanData: string) => {
|
||||
if (isUpdatingFromFormRef.current || !scanData.trim()) return;
|
||||
|
||||
isUpdatingFromQrRef.current = true;
|
||||
const parsedParams = parseQrCodeInfo(scanData);
|
||||
|
||||
const updatedTask = {
|
||||
...task,
|
||||
parameters: {
|
||||
...task.parameters,
|
||||
...parsedParams,
|
||||
},
|
||||
};
|
||||
}, [task.parameters.locationBay, task, onLocationBayChange]);
|
||||
|
||||
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: {
|
||||
@ -84,6 +155,11 @@ const TaskForm: React.FC<TaskFormProps> = ({
|
||||
},
|
||||
};
|
||||
onTaskChange(updatedTask);
|
||||
|
||||
// 重置标志
|
||||
setTimeout(() => {
|
||||
isUpdatingFromFormRef.current = false;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const openBottomSheet = (
|
||||
@ -113,10 +189,43 @@ const TaskForm: React.FC<TaskFormProps> = ({
|
||||
|
||||
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(
|
||||
@ -136,6 +245,7 @@ const TaskForm: React.FC<TaskFormProps> = ({
|
||||
label="途经点 (可选)"
|
||||
value={task.parameters.waypoint || ''}
|
||||
onChangeText={text => handleParamChange('waypoint', text)}
|
||||
onFocus={handleOtherInputFocus}
|
||||
/>
|
||||
|
||||
{renderDropdown(
|
||||
@ -146,15 +256,13 @@ const TaskForm: React.FC<TaskFormProps> = ({
|
||||
)}
|
||||
{renderDropdown('payload', '载荷', task.parameters.payload, PAYLOADS)}
|
||||
|
||||
{/* 库位字段改为普通输入框 */}
|
||||
{/* 库位字段 */}
|
||||
<Input
|
||||
ref={locationBayInputRef}
|
||||
label="库位"
|
||||
value={task.parameters.locationBay || ''}
|
||||
onChangeText={text => handleParamChange('locationBay', text)}
|
||||
showSoftInputOnFocus={false}
|
||||
autoFocus={false}
|
||||
placeholder="请输入库位"
|
||||
onFocus={handleOtherInputFocus}
|
||||
/>
|
||||
|
||||
<BottomSheet
|
||||
@ -185,6 +293,25 @@ 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;
|
||||
|
@ -65,15 +65,6 @@ export default function TaskEditScreen() {
|
||||
setIsModified(false);
|
||||
};
|
||||
|
||||
// 处理库位变化,自动运行任务
|
||||
const handleLocationBayChange = (updatedTask: Task) => {
|
||||
console.log(
|
||||
'库位已变化,自动运行任务:',
|
||||
updatedTask.parameters.locationBay,
|
||||
);
|
||||
runTask(updatedTask.id);
|
||||
};
|
||||
|
||||
if (!task) {
|
||||
return (
|
||||
<Dialog isVisible={true}>
|
||||
@ -84,11 +75,7 @@ export default function TaskEditScreen() {
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TaskForm
|
||||
task={task}
|
||||
onTaskChange={handleTaskChange}
|
||||
onLocationBayChange={handleLocationBayChange}
|
||||
/>
|
||||
<TaskForm task={task} onTaskChange={handleTaskChange} />
|
||||
<BottomActionBar
|
||||
onRun={handleRun}
|
||||
onSave={handleSave}
|
||||
|
Loading…
x
Reference in New Issue
Block a user