Enhance App structure by integrating TasksProvider; update BottomActionBar and TaskCard components to use React Native Paper; implement dropdowns in TaskForm; refactor TaskEditScreen and TaskListScreen to utilize context for task management.

This commit is contained in:
xudan 2025-07-21 15:36:11 +08:00
parent ea68167afb
commit 5ecb12a679
10 changed files with 298 additions and 120 deletions

View File

@ -2,14 +2,17 @@ import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import AppNavigator from './src/navigation/AppNavigator';
import { TasksProvider } from './src/context/TasksContext';
import { PaperProvider } from 'react-native-paper';
export default function App() {
return (
<PaperProvider>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
<TasksProvider>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
</TasksProvider>
</PaperProvider>
);
}

18
package-lock.json generated
View File

@ -17,6 +17,7 @@
"react-native-gesture-handler": "^2.27.1",
"react-native-get-random-values": "^1.11.0",
"react-native-paper": "^5.14.5",
"react-native-paper-dropdown": "^2.3.1",
"react-native-safe-area-context": "^5.5.2",
"react-native-screens": "^4.13.1",
"react-native-vector-icons": "^10.2.0",
@ -10826,6 +10827,23 @@
"react-native-safe-area-context": "*"
}
},
"node_modules/react-native-paper-dropdown": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/react-native-paper-dropdown/-/react-native-paper-dropdown-2.3.1.tgz",
"integrity": "sha512-IvcHTucAV5+fiX2IVMiVdBDKT6KHxycW0o9QzZe7bpmeZWmuCajHDnwG3OSBGlXhUxrrM3TC0/HJZHwORWGgQg==",
"license": "MIT",
"workspaces": [
"example"
],
"dependencies": {
"react-native-paper": "^5.12.3"
},
"peerDependencies": {
"react": "*",
"react-native": "*",
"react-native-paper": "*"
}
},
"node_modules/react-native-paper/node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz",

View File

@ -19,6 +19,7 @@
"react-native-gesture-handler": "^2.27.1",
"react-native-get-random-values": "^1.11.0",
"react-native-paper": "^5.14.5",
"react-native-paper-dropdown": "^2.3.1",
"react-native-safe-area-context": "^5.5.2",
"react-native-screens": "^4.13.1",
"react-native-vector-icons": "^10.2.0",

View File

@ -1,5 +1,6 @@
import React from 'react';
import { View, Button, StyleSheet } from 'react-native';
import { Appbar, useTheme } from 'react-native-paper';
import { StyleSheet } from 'react-native';
interface BottomActionBarProps {
onRun: () => void;
@ -18,25 +19,26 @@ const BottomActionBar: React.FC<BottomActionBarProps> = ({
onBack,
isSaveDisabled = true,
}) => {
const theme = useTheme();
return (
<View style={styles.container}>
<Button title="运行" onPress={onRun} />
<Button title="保存" onPress={onSave} disabled={isSaveDisabled} />
<Button title="撤销" onPress={onUndo} />
<Button title="恢复" onPress={onRestore} />
<Button title="返回" onPress={onBack} />
</View>
<Appbar style={[styles.bottom, { backgroundColor: theme.colors.elevation.level2 }]}>
<Appbar.Action icon="arrow-left" onPress={onBack} />
<Appbar.Action icon="play" onPress={onRun} />
<Appbar.Action icon="content-save" onPress={onSave} disabled={isSaveDisabled} />
<Appbar.Action icon="undo" onPress={onUndo} />
<Appbar.Action icon="restore" onPress={onRestore} />
</Appbar>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
bottom: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
justifyContent: 'space-around',
padding: 16,
borderTopWidth: 1,
borderColor: '#ccc',
backgroundColor: '#f5f5f5',
},
});

View File

@ -1,43 +1,52 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Task } from '../types/task';
import { StyleSheet } from 'react-native';
import { Card, Text, Chip, useTheme } from 'react-native-paper';
import { Task, TaskStatus } from '../types/task';
interface TaskCardProps {
task: Task;
onPress: (id: string) => void;
}
const statusColors: Record<TaskStatus, string> = {
IDLE: 'grey',
RUNNING: 'blue',
COMPLETED: 'green',
ERROR: 'red',
};
const TaskCard: React.FC<TaskCardProps> = ({ task, onPress }) => {
const theme = useTheme();
return (
<TouchableOpacity style={styles.card} onPress={() => onPress(task.id)}>
<Text style={styles.title}>{task.name}</Text>
<Text style={styles.status}>: {task.status}</Text>
</TouchableOpacity>
<Card style={styles.card} onPress={() => onPress(task.id)}>
<Card.Content>
<Text variant="titleMedium" style={styles.title}>{task.name}</Text>
<Chip
icon="information"
mode="outlined"
selectedColor={statusColors[task.status]}
style={[styles.chip, {backgroundColor: theme.colors.surface}]}
>
{task.status}
</Chip>
</Card.Content>
</Card>
);
};
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: 8,
padding: 16,
margin: 8,
width: '45%',
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
},
status: {
fontSize: 14,
color: '#666',
marginBottom: 12,
minHeight: 50, // Ensure cards have similar height
},
chip: {
alignSelf: 'flex-start',
}
});
export default TaskCard;

View File

@ -1,6 +1,9 @@
import React from 'react';
import { View, Text, TextInput, StyleSheet, ScrollView } from 'react-native';
import React, { useState } from 'react';
import { View, StyleSheet, ScrollView } from 'react-native';
import { TextInput } from 'react-native-paper';
import DropDown from "react-native-paper-dropdown";
import { Task, RobotAction } from '../types/task';
import { LOCATIONS, ROBOT_ACTIONS, PAYLOADS } from '../data/mockData';
interface TaskFormProps {
task: Task;
@ -8,7 +11,12 @@ interface TaskFormProps {
}
const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
const handleParamChange = (field: string, value: string) => {
const [showStartLocation, setShowStartLocation] = useState(false);
const [showEndLocation, setShowEndLocation] = useState(false);
const [showRobotAction, setShowRobotAction] = useState(false);
const [showPayload, setShowPayload] = useState(false);
const handleParamChange = (field: string, value: string | RobotAction) => {
const updatedTask = {
...task,
parameters: {
@ -20,85 +28,120 @@ const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
};
return (
<ScrollView style={styles.container}>
<View style={styles.fieldContainer}>
<Text style={styles.label}></Text>
<ScrollView contentContainerStyle={styles.container}>
<TextInput
style={styles.input}
value={task.name}
onChangeText={text => onTaskChange({ ...task, name: text })}
label="任务名称"
value={task.name}
onChangeText={text => onTaskChange({ ...task, name: text })}
style={styles.input}
mode="outlined"
/>
</View>
<View style={styles.fieldContainer}>
<Text style={styles.label}></Text>
{/* <DropDown
label={"起点"}
mode={"outlined"}
visible={showStartLocation}
showDropDown={() => setShowStartLocation(true)}
onDismiss={() => setShowStartLocation(false)}
value={task.parameters.startLocation}
setValue={(value) => handleParamChange('startLocation', value)}
list={LOCATIONS.map((location) => ({
label: location.label,
value: location.value,
}))}
dropDownStyle={styles.input}
/> */}
<TextInput
style={styles.input}
value={task.parameters.startLocation}
onChangeText={text => handleParamChange('startLocation', text)}
label="起点"
value={task.parameters.startLocation}
onChangeText={text => handleParamChange('startLocation', text)}
style={styles.input}
mode="outlined"
/>
</View>
<View style={styles.fieldContainer}>
<Text style={styles.label}></Text>
{/* <DropDown
label={"终点"}
mode={"outlined"}
visible={showEndLocation}
showDropDown={() => setShowEndLocation(true)}
onDismiss={() => setShowEndLocation(false)}
value={task.parameters.endLocation}
setValue={(value) => handleParamChange('endLocation', value)}
list={LOCATIONS.map((location) => ({
label: location.label,
value: location.value,
}))}
dropDownStyle={styles.input}
/> */}
<TextInput
style={styles.input}
value={task.parameters.endLocation}
onChangeText={text => handleParamChange('endLocation', text)}
label="终点"
value={task.parameters.endLocation}
onChangeText={text => handleParamChange('endLocation', text)}
style={styles.input}
mode="outlined"
/>
</View>
<View style={styles.fieldContainer}>
<Text style={styles.label}></Text>
<TextInput
style={styles.input}
value={task.parameters.waypoint || ''}
onChangeText={text => handleParamChange('waypoint', text)}
placeholder="可选"
label="途经点 (可选)"
value={task.parameters.waypoint || ''}
onChangeText={text => handleParamChange('waypoint', text)}
style={styles.input}
mode="outlined"
/>
{/* <DropDown
label={"机器人动作"}
mode={"outlined"}
visible={showRobotAction}
showDropDown={() => setShowRobotAction(true)}
onDismiss={() => setShowRobotAction(false)}
value={task.parameters.robotAction}
setValue={(value) => handleParamChange('robotAction', value)}
list={ROBOT_ACTIONS.map((action) => ({
label: action.label,
value: action.value,
}))}
dropDownStyle={styles.input}
/> */}
<TextInput
label="机器人动作"
value={task.parameters.robotAction}
onChangeText={text => handleParamChange('robotAction', text)}
style={styles.input}
mode="outlined"
/>
</View>
<View style={styles.fieldContainer}>
<Text style={styles.label}></Text>
{/* 在实际应用中,这里最好是一个下拉选择器 */}
{/* <DropDown
label={"载荷"}
mode={"outlined"}
visible={showPayload}
showDropDown={() => setShowPayload(true)}
onDismiss={() => setShowPayload(false)}
value={task.parameters.payload}
setValue={(value) => handleParamChange('payload', value)}
list={PAYLOADS.map((payload) => ({
label: payload.label,
value: payload.value,
}))}
dropDownStyle={styles.input}
/> */}
<TextInput
style={styles.input}
value={task.parameters.robotAction}
onChangeText={text => handleParamChange('robotAction', text as RobotAction)}
label="载荷"
value={task.parameters.payload}
onChangeText={text => handleParamChange('payload', text)}
style={styles.input}
mode="outlined"
/>
</View>
<View style={styles.fieldContainer}>
<Text style={styles.label}></Text>
<TextInput
style={styles.input}
value={task.parameters.payload}
onChangeText={text => handleParamChange('payload', text)}
/>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
fieldContainer: {
marginBottom: 16,
},
label: {
fontSize: 16,
marginBottom: 8,
fontWeight: 'bold',
},
input: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 4,
padding: 12,
fontSize: 16,
marginBottom: 16,
},
});

View File

@ -0,0 +1,52 @@
import React, { createContext, useState, useContext, ReactNode } from 'react';
import { Task } from '../types/task';
import { MOCK_TASKS } from '../data/mockData';
interface TasksContextData {
tasks: Task[];
getTaskById: (id: string) => Task | undefined;
updateTask: (updatedTask: Task) => void;
runTask: (id: string) => void;
}
const TasksContext = createContext<TasksContextData>({} as TasksContextData);
export const TasksProvider: React.FC<{children: ReactNode}> = ({ children }) => {
const [tasks, setTasks] = useState<Task[]>(MOCK_TASKS);
const getTaskById = (id: string) => {
return tasks.find(task => task.id === id);
};
const updateTask = (updatedTask: Task) => {
setTasks(prevTasks =>
prevTasks.map(task => (task.id === updatedTask.id ? updatedTask : task))
);
};
const runTask = (id: string) => {
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === id ? { ...task, status: 'RUNNING' } : task
)
);
// 模拟任务完成
setTimeout(() => {
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === id ? { ...task, status: 'COMPLETED' } : task
)
);
}, 5000);
};
return (
<TasksContext.Provider value={{ tasks, getTaskById, updateTask, runTask }}>
{children}
</TasksContext.Provider>
);
};
export function useTasks() {
return useContext(TasksContext);
}

View File

@ -1,6 +1,30 @@
import { Task } from '../types/task';
import { Task, RobotAction } from '../types/task';
import { v4 as uuidv4 } from 'uuid';
export const LOCATIONS = [
{ label: '炉前缓存区', value: '炉前缓存区' },
{ label: '热处理上料交接区', value: '热处理上料交接区' },
{ label: '空料架缓存区', value: '空料架缓存区' },
{ label: '焊接空料架交接区', value: '焊接空料架交接区' },
{ label: '热后区', value: '热后区' },
{ label: 'ALD上料区', value: 'ALD上料区' },
{ label: '空车区', value: '空车区' },
];
export const ROBOT_ACTIONS: { label: string; value: RobotAction }[] = [
{ label: '运输', value: 'TRANSPORT' },
{ label: '取货', value: 'PICKUP' },
{ label: '卸货', value: 'DROPOFF' },
{ label: '等待', value: 'WAIT' },
];
export const PAYLOADS = [
{ label: '满料架-A1', value: '满料架-A1' },
{ label: '空料架-B2', value: '空料架-B2' },
{ label: '空料架-C3', value: '空料架-C3' },
{ label: '空车', value: '空车' },
];
export const MOCK_TASKS: Task[] = [
{
id: uuidv4(),

View File

@ -2,11 +2,11 @@ import React, { useState, useEffect } from 'react';
import { View, StyleSheet, Alert } from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import { RouteProp } from '@react-navigation/native';
import { useTasks } from '../context/TasksContext';
import TaskForm from '../components/TaskForm';
import BottomActionBar from '../components/BottomActionBar';
import { Task } from '../types/task';
import { MOCK_TASKS } from '../data/mockData';
import { ActivityIndicator } from 'react-native-paper';
type RootStackParamList = {
TaskEdit: { taskId: string };
@ -19,16 +19,19 @@ export default function TaskEditScreen() {
const navigation = useNavigation();
const { taskId } = route.params;
const { getTaskById, updateTask, runTask } = useTasks();
const [task, setTask] = useState<Task | null>(null);
const [originalTask, setOriginalTask] = useState<Task | null>(null);
const [isModified, setIsModified] = useState(false);
useEffect(() => {
// 根据taskId从模拟数据中查找任务
const foundTask = MOCK_TASKS.find(t => t.id === taskId);
const foundTask = getTaskById(taskId);
if (foundTask) {
setTask(foundTask);
setOriginalTask(foundTask);
}
}, [taskId]);
}, [taskId, getTaskById]);
const handleTaskChange = (updatedTask: Task) => {
setTask(updatedTask);
@ -38,23 +41,43 @@ export default function TaskEditScreen() {
};
const handleSave = () => {
// 在这里处理保存逻辑比如调用API
Alert.alert('已保存', `任务 "${task?.name}" 已被保存。`);
setIsModified(false);
if (task) {
updateTask(task);
setOriginalTask(task);
setIsModified(false);
Alert.alert('已保存', `任务 "${task.name}" 已被保存。`);
}
};
const handleRun = () => {
if(task) {
runTask(task.id);
navigation.goBack();
}
}
const handleUndo = () => {
setTask(originalTask);
setIsModified(false);
}
const handleRestore = () => {
setTask(originalTask);
setIsModified(false);
}
if (!task) {
return null; // 或者显示一个加载指示器
return <ActivityIndicator animating={true} size="large" style={styles.loader}/>;
}
return (
<View style={styles.container}>
<TaskForm task={task} onTaskChange={handleTaskChange} />
<BottomActionBar
onRun={() => Alert.alert('运行', '开始运行任务...')}
onRun={handleRun}
onSave={handleSave}
onUndo={() => Alert.alert('撤销', '撤销更改...')}
onRestore={() => Alert.alert('恢复', '恢复到初始状态...')}
onUndo={handleUndo}
onRestore={handleRestore}
onBack={() => navigation.goBack()}
isSaveDisabled={!isModified}
/>
@ -67,4 +90,9 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: '#fff',
},
loader: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
});

View File

@ -1,10 +1,9 @@
import React, { useState } from 'react';
import React from 'react';
import { View, FlatList, StyleSheet } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { useTasks } from '../context/TasksContext';
import TaskCard from '../components/TaskCard';
import { Task } from '../types/task';
import { MOCK_TASKS } from '../data/mockData';
type RootStackParamList = {
TaskList: undefined;
@ -14,7 +13,7 @@ type RootStackParamList = {
type TaskListNavigationProp = StackNavigationProp<RootStackParamList, 'TaskList'>;
export default function TaskListScreen() {
const [tasks] = useState<Task[]>(MOCK_TASKS);
const { tasks } = useTasks();
const navigation = useNavigation<TaskListNavigationProp>();
const handlePressTask = (taskId: string) => {
@ -28,7 +27,7 @@ export default function TaskListScreen() {
renderItem={({ item }) => <TaskCard task={item} onPress={handlePressTask} />}
keyExtractor={item => item.id}
numColumns={2}
columnWrapperStyle={styles.row}
contentContainerStyle={styles.list}
/>
</View>
);
@ -37,9 +36,8 @@ export default function TaskListScreen() {
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
row: {
justifyContent: 'center',
},
list: {
padding: 8,
}
});