feat: 添加 @react-native-picker/picker 依赖,更新任务表单以支持动态选择参数,优化任务卡片和底部操作栏样式
This commit is contained in:
parent
d5b2158c1d
commit
de9d2ad3df
14
package-lock.json
generated
14
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||||
|
"@react-native-picker/picker": "^2.11.1",
|
||||||
"@react-native/new-app-screen": "0.80.1",
|
"@react-native/new-app-screen": "0.80.1",
|
||||||
"@react-navigation/bottom-tabs": "^7.4.2",
|
"@react-navigation/bottom-tabs": "^7.4.2",
|
||||||
"@react-navigation/native": "^7.1.14",
|
"@react-navigation/native": "^7.1.14",
|
||||||
@ -2989,6 +2990,19 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native-picker/picker": {
|
||||||
|
"version": "2.11.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@react-native-picker/picker/-/picker-2.11.1.tgz",
|
||||||
|
"integrity": "sha512-ThklnkK4fV3yynnIIRBkxxjxR4IFbdMNJVF6tlLdOJ/zEFUEFUEdXY0KmH0iYzMwY8W4/InWsLiA7AkpAbnexA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"example"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native/assets-registry": {
|
"node_modules/@react-native/assets-registry": {
|
||||||
"version": "0.80.1",
|
"version": "0.80.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@react-native/assets-registry/-/assets-registry-0.80.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@react-native/assets-registry/-/assets-registry-0.80.1.tgz",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||||
|
"@react-native-picker/picker": "^2.11.1",
|
||||||
"@react-native/new-app-screen": "0.80.1",
|
"@react-native/new-app-screen": "0.80.1",
|
||||||
"@react-navigation/bottom-tabs": "^7.4.2",
|
"@react-navigation/bottom-tabs": "^7.4.2",
|
||||||
"@react-navigation/native": "^7.1.14",
|
"@react-navigation/native": "^7.1.14",
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, StyleSheet } from 'react-native';
|
import { View, StyleSheet, TouchableOpacity, Text } from 'react-native';
|
||||||
import { Button, useTheme } from '@rneui/themed';
|
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
|
||||||
interface BottomActionBarProps {
|
interface BottomActionBarProps {
|
||||||
onRun: () => void;
|
onRun: () => void;
|
||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
onUndo: () => void;
|
onUndo: () => void;
|
||||||
onRestore: () => void;
|
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
isSaveDisabled?: boolean;
|
isSaveDisabled?: boolean;
|
||||||
}
|
}
|
||||||
@ -16,34 +14,78 @@ const BottomActionBar: React.FC<BottomActionBarProps> = ({
|
|||||||
onRun,
|
onRun,
|
||||||
onSave,
|
onSave,
|
||||||
onUndo,
|
onUndo,
|
||||||
onRestore,
|
|
||||||
onBack,
|
onBack,
|
||||||
isSaveDisabled = true,
|
isSaveDisabled = true,
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.bottom, { backgroundColor: theme.colors.background }]}>
|
<View style={styles.container}>
|
||||||
<Button type="clear" onPress={onBack} icon={<Icon name="arrow-left" size={24} color={theme.colors.primary} />} />
|
<TouchableOpacity onPress={onBack} style={styles.button}>
|
||||||
<Button type="clear" onPress={onRun} icon={<Icon name="play" size={24} color={theme.colors.primary} />} />
|
<Icon name="arrow-left" size={24} color="#AAAAAA" />
|
||||||
<Button type="clear" onPress={onSave} disabled={isSaveDisabled} icon={<Icon name="content-save" size={24} color={isSaveDisabled ? theme.colors.disabled : theme.colors.primary} />} />
|
<Text style={styles.buttonText}>返回</Text>
|
||||||
<Button type="clear" onPress={onUndo} icon={<Icon name="undo" size={24} color={theme.colors.primary} />} />
|
</TouchableOpacity>
|
||||||
<Button type="clear" onPress={onRestore} icon={<Icon name="restore" size={24} color={theme.colors.primary} />} />
|
<TouchableOpacity onPress={onUndo} style={styles.button}>
|
||||||
|
<Icon name="undo" size={24} color="#AAAAAA" />
|
||||||
|
<Text style={styles.buttonText}>撤销</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onSave}
|
||||||
|
disabled={isSaveDisabled}
|
||||||
|
style={[styles.button, isSaveDisabled && styles.disabledButton]}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="content-save"
|
||||||
|
size={24}
|
||||||
|
color={isSaveDisabled ? '#555555' : '#00ff00'}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.buttonText,
|
||||||
|
{ color: isSaveDisabled ? '#555555' : '#00ff00' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onRun}
|
||||||
|
style={[styles.button, styles.runButton]}
|
||||||
|
>
|
||||||
|
<Icon name="play" size={24} color="#FFFFFF" />
|
||||||
|
<Text style={[styles.buttonText, styles.runButtonText]}>运行</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
bottom: {
|
container: {
|
||||||
position: 'absolute',
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-around',
|
justifyContent: 'space-around',
|
||||||
paddingVertical: 8,
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
backgroundColor: 'rgba(26, 26, 26, 0.9)',
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderTopColor: '#e0e0e0',
|
borderTopColor: '#333333',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
color: '#AAAAAA',
|
||||||
|
fontSize: 12,
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
runButton: {
|
||||||
|
backgroundColor: '#007BFF',
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderRadius: 20,
|
||||||
|
},
|
||||||
|
runButtonText: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
disabledButton: {
|
||||||
|
opacity: 0.5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
|
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||||
import { Card, Chip } from '@rneui/themed';
|
|
||||||
import { Task } from '../types/task';
|
import { Task } from '../types/task';
|
||||||
|
|
||||||
interface TaskCardProps {
|
interface TaskCardProps {
|
||||||
@ -9,60 +8,75 @@ interface TaskCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const statusMap: { [key: number]: { text: string; color: string } } = {
|
const statusMap: { [key: number]: { text: string; color: string } } = {
|
||||||
0: { text: 'IDLE', color: 'grey' },
|
0: { text: '待机', color: '#9E9E9E' }, // Grey
|
||||||
1: { text: 'RUNNING', color: 'blue' },
|
1: { text: '运行中', color: '#2196F3' }, // Blue
|
||||||
2: { text: 'COMPLETED', color: 'green' },
|
2: { text: '完成', color: '#4CAF50' }, // Green
|
||||||
3: { text: 'ERROR', color: 'red' },
|
3: { text: '失败', color: '#F44336' }, // Red
|
||||||
};
|
};
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
|
||||||
const cardWidth = (width - 32) / 2; // 减去padding,除以2得到每个卡片的宽度
|
|
||||||
|
|
||||||
const TaskCard: React.FC<TaskCardProps> = ({ task, onPress }) => {
|
const TaskCard: React.FC<TaskCardProps> = ({ task, onPress }) => {
|
||||||
const statusInfo = statusMap[task.status] || statusMap[0];
|
const statusInfo = statusMap[task.status] || statusMap[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={() => onPress(task.id)} style={styles.touchable}>
|
<TouchableOpacity onPress={() => onPress(task.id)} style={styles.container}>
|
||||||
<Card containerStyle={styles.card}>
|
<View style={styles.indicatorContainer}>
|
||||||
<Card.Title style={styles.title}>{task.label}</Card.Title>
|
<View
|
||||||
<Card.Divider />
|
style={[
|
||||||
<Chip
|
styles.statusIndicator,
|
||||||
title={statusInfo.text}
|
{ backgroundColor: statusInfo.color },
|
||||||
icon={{
|
]}
|
||||||
name: 'information',
|
|
||||||
type: 'material-community',
|
|
||||||
size: 20,
|
|
||||||
color: statusInfo.color,
|
|
||||||
}}
|
|
||||||
type="outline"
|
|
||||||
containerStyle={styles.chip}
|
|
||||||
titleStyle={[styles.chipTitle, { color: statusInfo.color }]}
|
|
||||||
buttonStyle={{ borderColor: statusInfo.color }}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</View>
|
||||||
|
<View style={styles.contentContainer}>
|
||||||
|
<Text style={styles.title}>{task.label}</Text>
|
||||||
|
<Text style={styles.description} numberOfLines={1}>
|
||||||
|
{task.description || '暂无描述'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.statusContainer}>
|
||||||
|
<Text style={[styles.statusText, { color: statusInfo.color }]}>
|
||||||
|
{statusInfo.text}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
touchable: {
|
container: {
|
||||||
width: cardWidth,
|
flexDirection: 'row',
|
||||||
marginBottom: 8,
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#1a1a1a',
|
||||||
|
paddingVertical: 16,
|
||||||
|
paddingHorizontal: 16,
|
||||||
},
|
},
|
||||||
card: {
|
indicatorContainer: {
|
||||||
margin: 4,
|
marginRight: 16,
|
||||||
borderRadius: 8,
|
},
|
||||||
width: cardWidth - 8, // 减去margin
|
statusIndicator: {
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
borderRadius: 6,
|
||||||
|
},
|
||||||
|
contentContainer: {
|
||||||
|
flex: 1,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
marginBottom: 12,
|
color: '#FFFFFF',
|
||||||
minHeight: 50, // Ensure cards have similar height
|
fontSize: 16,
|
||||||
textAlign: 'left',
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 4,
|
||||||
},
|
},
|
||||||
chip: {
|
description: {
|
||||||
alignSelf: 'flex-start',
|
color: '#999999',
|
||||||
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
chipTitle: {
|
statusContainer: {
|
||||||
fontSize: 12,
|
marginLeft: 16,
|
||||||
|
},
|
||||||
|
statusText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, ScrollView, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import { Input } from '@rneui/themed';
|
import { Input, Slider } from '@rneui/themed';
|
||||||
|
// @ts-ignore
|
||||||
|
import { Picker } from '@react-native-picker/picker';
|
||||||
|
import { useTasks } from '../context/TasksContext';
|
||||||
import { Task, RobotAction, InputParam } from '../types/task';
|
import { Task, RobotAction, InputParam } from '../types/task';
|
||||||
|
|
||||||
interface TaskFormProps {
|
interface TaskFormProps {
|
||||||
@ -9,11 +12,12 @@ interface TaskFormProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
|
const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
|
||||||
useEffect(() => {
|
const { locations, payloads, robotActions, locationsBays } = useTasks();
|
||||||
console.log('TaskForm task prop updated:', task);
|
|
||||||
}, [task]);
|
|
||||||
|
|
||||||
const handleParamChange = (field: string, value: string | RobotAction) => {
|
const handleParamChange = (
|
||||||
|
field: string,
|
||||||
|
value: string | number | RobotAction,
|
||||||
|
) => {
|
||||||
const currentParam = task.parameters[field] || {};
|
const currentParam = task.parameters[field] || {};
|
||||||
const updatedTask = {
|
const updatedTask = {
|
||||||
...task,
|
...task,
|
||||||
@ -30,10 +34,10 @@ const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
|
|||||||
|
|
||||||
const renderFormInput = (param: InputParam) => {
|
const renderFormInput = (param: InputParam) => {
|
||||||
const parameter = task.parameters?.[param.name];
|
const parameter = task.parameters?.[param.name];
|
||||||
const value = parameter?.value || param.defaultValue;
|
const value = parameter?.value ?? param.defaultValue;
|
||||||
|
|
||||||
const label = (
|
const label = (
|
||||||
<Text>
|
<Text style={styles.label}>
|
||||||
{param.label}
|
{param.label}
|
||||||
{param.required && <Text style={styles.required}> *</Text>}
|
{param.required && <Text style={styles.required}> *</Text>}
|
||||||
</Text>
|
</Text>
|
||||||
@ -42,54 +46,219 @@ const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
|
|||||||
switch (param.type.toLowerCase()) {
|
switch (param.type.toLowerCase()) {
|
||||||
case 'string':
|
case 'string':
|
||||||
return (
|
return (
|
||||||
<Input
|
<View key={param.name} style={styles.inputGroup}>
|
||||||
key={param.name}
|
{label}
|
||||||
label={label}
|
<Input
|
||||||
value={value as string}
|
value={value as string}
|
||||||
onChangeText={text => handleParamChange(param.name, text)}
|
onChangeText={text => handleParamChange(param.name, text)}
|
||||||
placeholder={param.remark}
|
placeholder={param.remark || '请输入...'}
|
||||||
errorMessage={param.remark}
|
inputContainerStyle={styles.inputContainer}
|
||||||
inputContainerStyle={styles.inputContainer}
|
inputStyle={styles.inputText}
|
||||||
/>
|
placeholderTextColor="#888"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
case 'location':
|
||||||
|
case 'location_bay':
|
||||||
|
const locationData =
|
||||||
|
param.type.toLowerCase() === 'location' ? locations : locationsBays;
|
||||||
|
return (
|
||||||
|
<View key={param.name} style={styles.inputGroup}>
|
||||||
|
{label}
|
||||||
|
<View style={styles.pickerContainer}>
|
||||||
|
<Picker
|
||||||
|
selectedValue={value as string}
|
||||||
|
onValueChange={(itemValue: any) =>
|
||||||
|
handleParamChange(param.name, itemValue)
|
||||||
|
}
|
||||||
|
style={styles.picker}
|
||||||
|
dropdownIconColor="#00ff00"
|
||||||
|
>
|
||||||
|
{locationData.map(loc => (
|
||||||
|
<Picker.Item
|
||||||
|
key={loc.value}
|
||||||
|
label={loc.label}
|
||||||
|
value={loc.value}
|
||||||
|
color="#000000"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
case 'payload':
|
||||||
|
return (
|
||||||
|
<View key={param.name} style={styles.inputGroup}>
|
||||||
|
{label}
|
||||||
|
<View style={styles.pickerContainer}>
|
||||||
|
<Picker
|
||||||
|
selectedValue={value as string}
|
||||||
|
onValueChange={(itemValue: any) =>
|
||||||
|
handleParamChange(param.name, itemValue)
|
||||||
|
}
|
||||||
|
style={styles.picker}
|
||||||
|
dropdownIconColor="#00ff00"
|
||||||
|
>
|
||||||
|
{payloads.map(p => (
|
||||||
|
<Picker.Item
|
||||||
|
key={p.value}
|
||||||
|
label={p.label}
|
||||||
|
value={p.value}
|
||||||
|
color="#000000"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
case 'robotaction':
|
||||||
|
return (
|
||||||
|
<View key={param.name} style={styles.inputGroup}>
|
||||||
|
{label}
|
||||||
|
<View style={styles.pickerContainer}>
|
||||||
|
<Picker
|
||||||
|
selectedValue={(value as RobotAction)?.name}
|
||||||
|
onValueChange={(itemValue: any) => {
|
||||||
|
const selectedAction = robotActions.find(
|
||||||
|
a => a.value === itemValue,
|
||||||
|
);
|
||||||
|
if (selectedAction) {
|
||||||
|
handleParamChange(param.name, {
|
||||||
|
name: selectedAction.label,
|
||||||
|
actionId: selectedAction.actionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={styles.picker}
|
||||||
|
dropdownIconColor="#00ff00"
|
||||||
|
>
|
||||||
|
{robotActions.map(action => (
|
||||||
|
<Picker.Item
|
||||||
|
key={action.actionId}
|
||||||
|
label={action.label}
|
||||||
|
value={action.value}
|
||||||
|
color="#000000"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
case 'number':
|
||||||
|
return (
|
||||||
|
<View key={param.name} style={styles.inputGroup}>
|
||||||
|
{label}
|
||||||
|
<Input
|
||||||
|
value={String(value)}
|
||||||
|
onChangeText={text =>
|
||||||
|
handleParamChange(param.name, text.replace(/[^0-9]/g, ''))
|
||||||
|
}
|
||||||
|
placeholder={param.remark || '请输入数字...'}
|
||||||
|
keyboardType="numeric"
|
||||||
|
inputContainerStyle={styles.inputContainer}
|
||||||
|
inputStyle={styles.inputText}
|
||||||
|
placeholderTextColor="#888"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
case 'slider':
|
||||||
|
return (
|
||||||
|
<View key={param.name} style={styles.inputGroup}>
|
||||||
|
{label}
|
||||||
|
<View style={styles.sliderContainer}>
|
||||||
|
<Slider
|
||||||
|
value={Number(value) || 0}
|
||||||
|
onValueChange={val => handleParamChange(param.name, val)}
|
||||||
|
minimumValue={param.min || 0}
|
||||||
|
maximumValue={param.max || 100}
|
||||||
|
step={param.step || 1}
|
||||||
|
allowTouchTrack
|
||||||
|
trackStyle={styles.sliderTrack}
|
||||||
|
thumbStyle={styles.sliderThumb}
|
||||||
|
/>
|
||||||
|
<Text style={styles.sliderValue}>{String(value)}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return null;
|
||||||
<Input
|
|
||||||
key={param.name}
|
|
||||||
label={label}
|
|
||||||
value={value as string}
|
|
||||||
onChangeText={text => handleParamChange(param.name, text)}
|
|
||||||
placeholder={param.remark}
|
|
||||||
errorMessage={param.remark}
|
|
||||||
inputContainerStyle={styles.inputContainer}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView contentContainerStyle={styles.container}>
|
<View style={styles.container}>
|
||||||
<Input
|
<View style={styles.inputGroup}>
|
||||||
label="任务名称"
|
<Text style={styles.label}>任务名称</Text>
|
||||||
value={task.name}
|
<Input
|
||||||
onChangeText={text => onTaskChange({ ...task, name: text })}
|
value={task.name}
|
||||||
inputContainerStyle={styles.inputContainer}
|
onChangeText={text => onTaskChange({ ...task, name: text })}
|
||||||
/>
|
inputContainerStyle={styles.inputContainer}
|
||||||
|
inputStyle={styles.inputText}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
{task.detail && task.detail.inputParams ? (
|
{task.detail && task.detail.inputParams ? (
|
||||||
task.detail.inputParams.map(param => renderFormInput(param))
|
task.detail.inputParams.map(param => renderFormInput(param))
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
<Text>正在加载任务参数...</Text>
|
<Text style={styles.loadingText}>正在加载任务参数...</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</ScrollView>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
// padding: 16, //
|
padding: 16,
|
||||||
|
},
|
||||||
|
inputGroup: {
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#AAAAAA',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
color: '#ff4d4d',
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
backgroundColor: '#333333',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#555555',
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
borderRadius: 5,
|
||||||
|
},
|
||||||
|
inputText: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
pickerContainer: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: 5,
|
||||||
|
},
|
||||||
|
picker: {
|
||||||
|
color: '#000000',
|
||||||
|
},
|
||||||
|
sliderContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
sliderTrack: {
|
||||||
|
height: 5,
|
||||||
|
backgroundColor: '#555',
|
||||||
|
},
|
||||||
|
sliderThumb: {
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
backgroundColor: '#00ff00',
|
||||||
|
},
|
||||||
|
sliderValue: {
|
||||||
|
marginLeft: 16,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
loadingContainer: {
|
loadingContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@ -97,11 +266,9 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: 20,
|
padding: 20,
|
||||||
},
|
},
|
||||||
required: {
|
loadingText: {
|
||||||
color: 'red',
|
color: '#AAAAAA',
|
||||||
},
|
fontSize: 16,
|
||||||
inputContainer: {
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
1
src/context/Tasks.tsx
Normal file
1
src/context/Tasks.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -4,6 +4,8 @@ import React, {
|
|||||||
useContext,
|
useContext,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Task } from '../types/task';
|
import { Task } from '../types/task';
|
||||||
import {
|
import {
|
||||||
@ -12,11 +14,7 @@ import {
|
|||||||
PayloadOption,
|
PayloadOption,
|
||||||
RobotActionOption,
|
RobotActionOption,
|
||||||
} from '../types/config';
|
} from '../types/config';
|
||||||
import {
|
import { getConfig, executeTask } from '../services/configService';
|
||||||
getConfig,
|
|
||||||
executeTask,
|
|
||||||
clearCachedConfig,
|
|
||||||
} from '../services/configService';
|
|
||||||
|
|
||||||
interface TasksContextData {
|
interface TasksContextData {
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
@ -30,6 +28,7 @@ interface TasksContextData {
|
|||||||
runTask: (id: string) => void;
|
runTask: (id: string) => void;
|
||||||
refreshConfig: () => Promise<void>;
|
refreshConfig: () => Promise<void>;
|
||||||
isConfigLoaded: boolean;
|
isConfigLoaded: boolean;
|
||||||
|
fetchTaskDetail: (taskId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TasksContext = createContext<TasksContextData>({} as TasksContextData);
|
const TasksContext = createContext<TasksContextData>({} as TasksContextData);
|
||||||
@ -45,7 +44,7 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
const [isConfigLoaded, setIsConfigLoaded] = useState(false);
|
const [isConfigLoaded, setIsConfigLoaded] = useState(false);
|
||||||
const [serverUrl, setServerUrl] = useState<string | null>(null);
|
const [serverUrl, setServerUrl] = useState<string | null>(null);
|
||||||
|
|
||||||
const fetchTasks = async (baseUrl: string, endpoint: string) => {
|
const fetchTasks = useCallback(async (baseUrl: string, endpoint: string) => {
|
||||||
try {
|
try {
|
||||||
if (baseUrl && endpoint) {
|
if (baseUrl && endpoint) {
|
||||||
const fetchUrl = `${baseUrl}${endpoint}?pageNum=1&pageSize=100`;
|
const fetchUrl = `${baseUrl}${endpoint}?pageNum=1&pageSize=100`;
|
||||||
@ -73,14 +72,14 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
return {
|
return {
|
||||||
...task,
|
...task,
|
||||||
name: task.label,
|
name: task.label,
|
||||||
parameters: task.parameters || {}, // Ensure parameters object exists
|
parameters: task.parameters || {},
|
||||||
detail: detail,
|
detail: detail,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setTasks(fetchedTasks);
|
setTasks(fetchedTasks);
|
||||||
} else {
|
} else {
|
||||||
console.error('获取任务列表失败: responseData.code is not 200');
|
console.error('获取任务列表失败: responseData.code is not 200');
|
||||||
setTasks([]); //
|
setTasks([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -88,17 +87,16 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error('Error message:', error.message);
|
console.error('Error message:', error.message);
|
||||||
}
|
}
|
||||||
setTasks([]); //
|
setTasks([]);
|
||||||
}
|
}
|
||||||
};
|
}, []); // Empty dependency array as it has no external dependencies from component scope
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadApp = async () => {
|
const loadApp = async () => {
|
||||||
try {
|
try {
|
||||||
await clearCachedConfig();
|
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
if (config) {
|
if (config) {
|
||||||
applyConfig(config); // Don't load tasks from config
|
applyConfig(config);
|
||||||
setIsConfigLoaded(true);
|
setIsConfigLoaded(true);
|
||||||
if (config.serverUrl && config.apiEndpoints) {
|
if (config.serverUrl && config.apiEndpoints) {
|
||||||
await fetchTasks(config.serverUrl, config.apiEndpoints.getTasks);
|
await fetchTasks(config.serverUrl, config.apiEndpoints.getTasks);
|
||||||
@ -114,7 +112,7 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadApp();
|
loadApp();
|
||||||
}, []);
|
}, [fetchTasks]);
|
||||||
|
|
||||||
const [apiEndpoints, setApiEndpoints] = useState<{
|
const [apiEndpoints, setApiEndpoints] = useState<{
|
||||||
getTasks: string;
|
getTasks: string;
|
||||||
@ -135,12 +133,12 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshConfig = async () => {
|
const refreshConfig = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
await clearCachedConfig();
|
// await clearCachedConfig(); // 通常刷新不需要清除缓存
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
if (config) {
|
if (config) {
|
||||||
applyConfig(config); // Don't load tasks from config
|
applyConfig(config);
|
||||||
setIsConfigLoaded(true);
|
setIsConfigLoaded(true);
|
||||||
if (config.serverUrl && config.apiEndpoints) {
|
if (config.serverUrl && config.apiEndpoints) {
|
||||||
await fetchTasks(config.serverUrl, config.apiEndpoints.getTasks);
|
await fetchTasks(config.serverUrl, config.apiEndpoints.getTasks);
|
||||||
@ -153,122 +151,144 @@ export const TasksProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
console.error('刷新配置失败:', error);
|
console.error('刷新配置失败:', error);
|
||||||
setIsConfigLoaded(false);
|
setIsConfigLoaded(false);
|
||||||
}
|
}
|
||||||
};
|
}, [fetchTasks]);
|
||||||
|
|
||||||
const getTaskById = (id: string) => {
|
const getTaskById = useCallback(
|
||||||
const task = tasks.find(t => t.id === id);
|
(id: string) => {
|
||||||
if (task && !task.detail) {
|
const task = tasks.find(t => t.id === id);
|
||||||
fetchTaskDetail(id);
|
if (task && !task.detail) {
|
||||||
}
|
// fetchTaskDetail(id); // 详情在编辑页面获取
|
||||||
return task;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchTaskDetail = async (taskId: string) => {
|
|
||||||
try {
|
|
||||||
if (serverUrl && apiEndpoints) {
|
|
||||||
const endpoint = apiEndpoints.getTaskDetail.replace('{taskId}', taskId);
|
|
||||||
const url = `${serverUrl}${endpoint}`;
|
|
||||||
console.log('Fetching task detail from:', url);
|
|
||||||
const response = await fetch(url);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseData = await response.json();
|
|
||||||
console.log('task detail responseData', responseData);
|
|
||||||
|
|
||||||
if (responseData && responseData.code === 200) {
|
|
||||||
let taskDetail = responseData.data.detail;
|
|
||||||
if (taskDetail && typeof taskDetail === 'string') {
|
|
||||||
try {
|
|
||||||
taskDetail = JSON.parse(taskDetail);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('解析任务详情失败 (detail):', e);
|
|
||||||
taskDetail = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTasks(prevTasks =>
|
|
||||||
prevTasks.map(task =>
|
|
||||||
task.id === taskId ? { ...task, detail: taskDetail } : task,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
'获取任务详情失败: responseData.code is not 200',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
return task;
|
||||||
console.error('获取任务详情失败:', error);
|
},
|
||||||
}
|
[tasks],
|
||||||
};
|
);
|
||||||
|
|
||||||
const updateTask = (updatedTask: Task) => {
|
const fetchTaskDetail = useCallback(
|
||||||
|
async (taskId: string) => {
|
||||||
|
try {
|
||||||
|
if (serverUrl && apiEndpoints) {
|
||||||
|
const endpoint = apiEndpoints.getTaskDetail.replace(
|
||||||
|
'{taskId}',
|
||||||
|
taskId,
|
||||||
|
);
|
||||||
|
const url = `${serverUrl}${endpoint}`;
|
||||||
|
console.log('Fetching task detail from:', url);
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await response.json();
|
||||||
|
console.log('task detail responseData', responseData);
|
||||||
|
|
||||||
|
if (responseData && responseData.code === 200) {
|
||||||
|
let taskDetail = responseData.data.detail;
|
||||||
|
if (taskDetail && typeof taskDetail === 'string') {
|
||||||
|
try {
|
||||||
|
taskDetail = JSON.parse(taskDetail);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析任务详情失败 (detail):', e);
|
||||||
|
taskDetail = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTasks(prevTasks =>
|
||||||
|
prevTasks.map(task =>
|
||||||
|
task.id === taskId ? { ...task, detail: taskDetail } : task,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error('获取任务详情失败: responseData.code is not 200');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取任务详情失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[serverUrl, apiEndpoints],
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateTask = useCallback((updatedTask: Task) => {
|
||||||
setTasks(prevTasks =>
|
setTasks(prevTasks =>
|
||||||
prevTasks.map(task => (task.id === updatedTask.id ? updatedTask : task)),
|
prevTasks.map(task => (task.id === updatedTask.id ? updatedTask : task)),
|
||||||
);
|
);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const runTask = async (id: string) => {
|
const runTask = useCallback(
|
||||||
const task = getTaskById(id);
|
async (id: string) => {
|
||||||
if (!task || !serverUrl || !apiEndpoints) return;
|
const task = tasks.find(t => t.id === id);
|
||||||
|
if (!task || !serverUrl || !apiEndpoints) return;
|
||||||
|
|
||||||
// 更新任务状态为运行中
|
|
||||||
setTasks(prevTasks =>
|
|
||||||
prevTasks.map(t => (t.id === id ? { ...t, status: 1 } : t)),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 提取参数值
|
|
||||||
const parameters = Object.entries(task.parameters).reduce(
|
|
||||||
(acc, [key, param]) => {
|
|
||||||
if (param) {
|
|
||||||
acc[key] = param.value;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as { [key: string]: any },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 获取服务器设置并发送任务执行请求
|
|
||||||
const endpoint = apiEndpoints.runTask.replace('{taskId}', task.id);
|
|
||||||
await executeTask(serverUrl, endpoint, {
|
|
||||||
name: task.name,
|
|
||||||
parameters: parameters,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 模拟任务完成(实际项目中应该通过WebSocket或轮询获取任务状态)
|
|
||||||
setTimeout(() => {
|
|
||||||
setTasks(prevTasks =>
|
|
||||||
prevTasks.map(t => (t.id === id ? { ...t, status: 2 } : t)),
|
|
||||||
);
|
|
||||||
}, 5000);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('任务执行失败:', error);
|
|
||||||
// 任务执行失败,更新状态为错误
|
|
||||||
setTasks(prevTasks =>
|
setTasks(prevTasks =>
|
||||||
prevTasks.map(t => (t.id === id ? { ...t, status: 3 } : t)),
|
prevTasks.map(t => (t.id === id ? { ...t, status: 1 } : t)),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
};
|
try {
|
||||||
|
const parameters = Object.entries(task.parameters).reduce(
|
||||||
|
(acc, [key, param]) => {
|
||||||
|
if (param) {
|
||||||
|
acc[key] = param.value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as { [key: string]: any },
|
||||||
|
);
|
||||||
|
|
||||||
|
const endpoint = apiEndpoints.runTask.replace('{taskId}', task.id);
|
||||||
|
await executeTask(serverUrl, endpoint, {
|
||||||
|
name: task.name,
|
||||||
|
parameters: parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setTasks(prevTasks =>
|
||||||
|
prevTasks.map(t => (t.id === id ? { ...t, status: 2 } : t)),
|
||||||
|
);
|
||||||
|
}, 5000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('任务执行失败:', error);
|
||||||
|
setTasks(prevTasks =>
|
||||||
|
prevTasks.map(t => (t.id === id ? { ...t, status: 3 } : t)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[tasks, serverUrl, apiEndpoints],
|
||||||
|
);
|
||||||
|
|
||||||
|
const contextValue = useMemo(
|
||||||
|
() => ({
|
||||||
|
tasks,
|
||||||
|
locations,
|
||||||
|
locationsBays,
|
||||||
|
payloads,
|
||||||
|
robotActions,
|
||||||
|
serverUrl,
|
||||||
|
getTaskById,
|
||||||
|
updateTask,
|
||||||
|
runTask,
|
||||||
|
refreshConfig,
|
||||||
|
isConfigLoaded,
|
||||||
|
fetchTaskDetail,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
tasks,
|
||||||
|
locations,
|
||||||
|
locationsBays,
|
||||||
|
payloads,
|
||||||
|
robotActions,
|
||||||
|
serverUrl,
|
||||||
|
getTaskById,
|
||||||
|
updateTask,
|
||||||
|
runTask,
|
||||||
|
refreshConfig,
|
||||||
|
isConfigLoaded,
|
||||||
|
fetchTaskDetail,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TasksContext.Provider
|
<TasksContext.Provider value={contextValue}>
|
||||||
value={{
|
|
||||||
tasks,
|
|
||||||
locations,
|
|
||||||
locationsBays,
|
|
||||||
payloads,
|
|
||||||
robotActions,
|
|
||||||
serverUrl,
|
|
||||||
getTaskById,
|
|
||||||
updateTask,
|
|
||||||
runTask,
|
|
||||||
refreshConfig,
|
|
||||||
isConfigLoaded,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</TasksContext.Provider>
|
</TasksContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { View, StyleSheet, Alert, ScrollView } from 'react-native';
|
import {
|
||||||
|
View,
|
||||||
|
StyleSheet,
|
||||||
|
Alert,
|
||||||
|
ScrollView,
|
||||||
|
ActivityIndicator,
|
||||||
|
} from 'react-native';
|
||||||
import { useRoute, useNavigation } from '@react-navigation/native';
|
import { useRoute, useNavigation } from '@react-navigation/native';
|
||||||
import { RouteProp } from '@react-navigation/native';
|
import { RouteProp } from '@react-navigation/native';
|
||||||
import { useTasks } from '../context/TasksContext';
|
import { useTasks } from '../context/TasksContext';
|
||||||
import TaskForm from '../components/TaskForm';
|
import TaskForm from '../components/TaskForm';
|
||||||
import BottomActionBar from '../components/BottomActionBar';
|
import BottomActionBar from '../components/BottomActionBar';
|
||||||
import { Task } from '../types/task';
|
import { Task } from '../types/task';
|
||||||
import { Dialog, Card } from '@rneui/themed';
|
|
||||||
|
|
||||||
type RootStackParamList = {
|
type RootStackParamList = {
|
||||||
TaskEdit: { taskId: string };
|
TaskEdit: { taskId: string };
|
||||||
@ -19,19 +24,28 @@ export default function TaskEditScreen() {
|
|||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const { taskId } = route.params;
|
const { taskId } = route.params;
|
||||||
|
|
||||||
const { getTaskById, updateTask, runTask, tasks } = useTasks();
|
const { getTaskById, updateTask, runTask, tasks, fetchTaskDetail } =
|
||||||
|
useTasks();
|
||||||
|
|
||||||
const [task, setTask] = useState<Task | null>(null);
|
const [task, setTask] = useState<Task | null>(null);
|
||||||
const [originalTask, setOriginalTask] = useState<Task | null>(null);
|
const [originalTask, setOriginalTask] = useState<Task | null>(null);
|
||||||
const [isModified, setIsModified] = useState(false);
|
const [isModified, setIsModified] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const taskData = getTaskById(taskId);
|
const loadTask = async () => {
|
||||||
if (taskData) {
|
let taskData = getTaskById(taskId);
|
||||||
setTask(taskData);
|
if (taskData && !taskData.detail) {
|
||||||
setOriginalTask(taskData);
|
await fetchTaskDetail(taskId);
|
||||||
}
|
// Re-fetch task data after details are loaded
|
||||||
}, [taskId, getTaskById, tasks]); // Add tasks to dependency array
|
taskData = getTaskById(taskId);
|
||||||
|
}
|
||||||
|
if (taskData) {
|
||||||
|
setTask(taskData);
|
||||||
|
setOriginalTask(taskData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadTask();
|
||||||
|
}, [taskId, getTaskById, tasks, fetchTaskDetail]);
|
||||||
|
|
||||||
const handleTaskChange = (updatedTask: Task) => {
|
const handleTaskChange = (updatedTask: Task) => {
|
||||||
setTask(updatedTask);
|
setTask(updatedTask);
|
||||||
@ -68,18 +82,16 @@ export default function TaskEditScreen() {
|
|||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
return (
|
return (
|
||||||
<Dialog isVisible={true}>
|
<View style={styles.loadingContainer}>
|
||||||
<Dialog.Loading />
|
<ActivityIndicator size="large" color="#00ff00" />
|
||||||
</Dialog>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<ScrollView>
|
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
||||||
<Card containerStyle={styles.card}>
|
<TaskForm task={task} onTaskChange={handleTaskChange} />
|
||||||
<TaskForm task={task} onTaskChange={handleTaskChange} />
|
|
||||||
</Card>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<BottomActionBar
|
<BottomActionBar
|
||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
@ -96,10 +108,15 @@ export default function TaskEditScreen() {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#f0f2f5',
|
backgroundColor: '#1a1a1a', // 深色背景
|
||||||
},
|
},
|
||||||
card: {
|
loadingContainer: {
|
||||||
borderRadius: 8,
|
flex: 1,
|
||||||
margin: 10,
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#1a1a1a',
|
||||||
|
},
|
||||||
|
scrollContainer: {
|
||||||
|
padding: 16,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, ScrollView, StyleSheet } from 'react-native';
|
import { View, StyleSheet, FlatList } from 'react-native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import { useTasks } from '../context/TasksContext';
|
import { useTasks } from '../context/TasksContext';
|
||||||
@ -17,23 +17,24 @@ type TaskListNavigationProp = StackNavigationProp<
|
|||||||
|
|
||||||
export default function TaskListScreen() {
|
export default function TaskListScreen() {
|
||||||
const { tasks } = useTasks();
|
const { tasks } = useTasks();
|
||||||
console.log('tasks', tasks);
|
|
||||||
|
|
||||||
const navigation = useNavigation<TaskListNavigationProp>();
|
const navigation = useNavigation<TaskListNavigationProp>();
|
||||||
|
|
||||||
const handlePressTask = (id: string) => {
|
const handlePressTask = (id: string) => {
|
||||||
navigation.navigate('TaskEdit', { taskId: id });
|
navigation.navigate('TaskEdit', { taskId: id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderItem = ({ item }: { item: (typeof tasks)[0] }) => (
|
||||||
|
<TaskCard task={item} onPress={handlePressTask} />
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
<FlatList
|
||||||
<View style={styles.tasksContainer}>
|
data={tasks}
|
||||||
{tasks.map(task => (
|
renderItem={renderItem}
|
||||||
<TaskCard key={task.id} task={task} onPress={handlePressTask} />
|
keyExtractor={item => item.id}
|
||||||
))}
|
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
||||||
</View>
|
/>
|
||||||
</ScrollView>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -41,13 +42,11 @@ export default function TaskListScreen() {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
backgroundColor: '#1a1a1a', // 深色背景
|
||||||
},
|
},
|
||||||
scrollContainer: {
|
separator: {
|
||||||
padding: 16,
|
height: 1,
|
||||||
},
|
backgroundColor: '#333', // 分隔线颜色
|
||||||
tasksContainer: {
|
marginLeft: 16,
|
||||||
flexDirection: 'row',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { Task, RobotAction } from './task';
|
|
||||||
|
|
||||||
// 配置文件中的位置选项
|
// 配置文件中的位置选项
|
||||||
export interface LocationOption {
|
export interface LocationOption {
|
||||||
label: string;
|
label: string;
|
||||||
@ -15,7 +13,8 @@ export interface PayloadOption {
|
|||||||
// 配置文件中的机器人动作选项
|
// 配置文件中的机器人动作选项
|
||||||
export interface RobotActionOption {
|
export interface RobotActionOption {
|
||||||
label: string;
|
label: string;
|
||||||
value: RobotAction;
|
value: string; // 动作的名称,作为 Picker 的 value
|
||||||
|
actionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 完整的配置文件结构
|
// 完整的配置文件结构
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
// 机器人的具体动作,可以定义为枚举或联合类型
|
// 机器人的具体动作,现在是一个对象
|
||||||
export type RobotAction =
|
export interface RobotAction {
|
||||||
| 'PICKUP'
|
name: string;
|
||||||
| 'DROPOFF'
|
actionId: string;
|
||||||
| 'TRANSPORT'
|
}
|
||||||
| 'WAIT'
|
|
||||||
| 'CHARGE'
|
|
||||||
| 'CLEAN';
|
|
||||||
|
|
||||||
|
|
||||||
// 参数选项接口
|
// 参数选项接口
|
||||||
export interface ParameterOption {
|
export interface ParameterOption {
|
||||||
@ -18,7 +14,7 @@ export interface ParameterOption {
|
|||||||
export interface DynamicParameter {
|
export interface DynamicParameter {
|
||||||
label?: string;
|
label?: string;
|
||||||
type?: 'Simple' | 'Select' | 'MultiSelect' | 'Text' | 'Number';
|
type?: 'Simple' | 'Select' | 'MultiSelect' | 'Text' | 'Number';
|
||||||
value: string | string[] | number;
|
value: string | string[] | number | RobotAction;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
options?: ParameterOption[];
|
options?: ParameterOption[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@ -36,6 +32,7 @@ export interface Task {
|
|||||||
id: string; // 唯一ID,例如使用 uuid
|
id: string; // 唯一ID,例如使用 uuid
|
||||||
name: string; // 任务名称, e.g., "炉前缓存区到热处理上料交接区运输"
|
name: string; // 任务名称, e.g., "炉前缓存区到热处理上料交接区运输"
|
||||||
label: string;
|
label: string;
|
||||||
|
description?: string;
|
||||||
version: number;
|
version: number;
|
||||||
templateName: string;
|
templateName: string;
|
||||||
periodicTask: boolean;
|
periodicTask: boolean;
|
||||||
@ -60,6 +57,9 @@ export interface InputParam {
|
|||||||
type: string;
|
type: string;
|
||||||
label: string;
|
label: string;
|
||||||
required: boolean;
|
required: boolean;
|
||||||
defaultValue: string;
|
defaultValue: any;
|
||||||
remark: string;
|
remark: string;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user