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