feat:替换组件库为react-native-elements

This commit is contained in:
xudan 2025-07-21 16:45:09 +08:00
parent a4372a12fc
commit 9481c802f1
9 changed files with 433 additions and 377 deletions

View File

@ -4,8 +4,11 @@
"Bash(npm install:*)", "Bash(npm install:*)",
"Bash(mkdir -p:*)", "Bash(mkdir -p:*)",
"Bash(npm install:*)", "Bash(npm install:*)",
"Bash(*:*)" "Bash(*:*)",
"Bash(npm uninstall react-native-paper react-native-paper-dropdown)",
"Bash(npm uninstall react-native-paper react-native-paper-dropdown --legacy-peer-deps)",
"Bash(npx react-native start --reset-cache)"
], ],
"deny": [] "deny": []
} }
} }

View File

@ -3,16 +3,16 @@ import { NavigationContainer } from '@react-navigation/native';
import AppNavigator from './src/navigation/AppNavigator'; import AppNavigator from './src/navigation/AppNavigator';
import { TasksProvider } from './src/context/TasksContext'; import { TasksProvider } from './src/context/TasksContext';
import { PaperProvider } from 'react-native-paper'; import { ThemeProvider } from '@rneui/themed';
export default function App() { export default function App() {
return ( return (
<PaperProvider> <ThemeProvider>
<TasksProvider> <TasksProvider>
<NavigationContainer> <NavigationContainer>
<AppNavigator /> <AppNavigator />
</NavigationContainer> </NavigationContainer>
</TasksProvider> </TasksProvider>
</PaperProvider> </ThemeProvider>
); );
} }

513
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,12 +14,12 @@
"@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",
"@react-navigation/stack": "^7.4.2", "@react-navigation/stack": "^7.4.2",
"@rneui/base": "^4.0.0-rc.8",
"@rneui/themed": "^4.0.0-rc.8",
"react": "19.1.0", "react": "19.1.0",
"react-native": "0.80.1", "react-native": "0.80.1",
"react-native-gesture-handler": "^2.27.1", "react-native-gesture-handler": "^2.27.1",
"react-native-get-random-values": "^1.11.0", "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-safe-area-context": "^5.5.2",
"react-native-screens": "^4.13.1", "react-native-screens": "^4.13.1",
"react-native-vector-icons": "^10.2.0", "react-native-vector-icons": "^10.2.0",

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { Appbar, useTheme } from 'react-native-paper'; import { View, StyleSheet } from 'react-native';
import { StyleSheet } from 'react-native'; import { Button, useTheme } from '@rneui/themed';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
interface BottomActionBarProps { interface BottomActionBarProps {
onRun: () => void; onRun: () => void;
@ -19,16 +20,16 @@ const BottomActionBar: React.FC<BottomActionBarProps> = ({
onBack, onBack,
isSaveDisabled = true, isSaveDisabled = true,
}) => { }) => {
const theme = useTheme(); const { theme } = useTheme();
return ( return (
<Appbar style={[styles.bottom, { backgroundColor: theme.colors.elevation.level2 }]}> <View style={[styles.bottom, { backgroundColor: theme.colors.background }]}>
<Appbar.Action icon="arrow-left" onPress={onBack} /> <Button type="clear" onPress={onBack} icon={<Icon name="arrow-left" size={24} color={theme.colors.primary} />} />
<Appbar.Action icon="play" onPress={onRun} /> <Button type="clear" onPress={onRun} icon={<Icon name="play" size={24} color={theme.colors.primary} />} />
<Appbar.Action icon="content-save" onPress={onSave} disabled={isSaveDisabled} /> <Button type="clear" onPress={onSave} disabled={isSaveDisabled} icon={<Icon name="content-save" size={24} color={isSaveDisabled ? theme.colors.disabled : theme.colors.primary} />} />
<Appbar.Action icon="undo" onPress={onUndo} /> <Button type="clear" onPress={onUndo} icon={<Icon name="undo" size={24} color={theme.colors.primary} />} />
<Appbar.Action icon="restore" onPress={onRestore} /> <Button type="clear" onPress={onRestore} icon={<Icon name="restore" size={24} color={theme.colors.primary} />} />
</Appbar> </View>
); );
}; };
@ -38,7 +39,11 @@ const styles = StyleSheet.create({
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
flexDirection: 'row',
justifyContent: 'space-around', justifyContent: 'space-around',
paddingVertical: 8,
borderTopWidth: 1,
borderTopColor: '#e0e0e0',
}, },
}); });

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import { StyleSheet } from 'react-native'; import { StyleSheet, TouchableOpacity } from 'react-native';
import { Card, Text, Chip, useTheme } from 'react-native-paper'; import { Card, Text, Chip, useTheme } from '@rneui/themed';
import { Task, TaskStatus } from '../types/task'; import { Task, TaskStatus } from '../types/task';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
interface TaskCardProps { interface TaskCardProps {
task: Task; task: Task;
@ -16,36 +17,40 @@ const statusColors: Record<TaskStatus, string> = {
}; };
const TaskCard: React.FC<TaskCardProps> = ({ task, onPress }) => { const TaskCard: React.FC<TaskCardProps> = ({ task, onPress }) => {
const theme = useTheme(); const { theme } = useTheme();
return ( return (
<Card style={styles.card} onPress={() => onPress(task.id)}> <TouchableOpacity onPress={() => onPress(task.id)} style={{ flex: 1, width: '50%' }}>
<Card.Content> <Card containerStyle={styles.card}>
<Text variant="titleMedium" style={styles.title}> <Card.Title style={styles.title}>{task.name}</Card.Title>
{task.name} <Card.Divider />
</Text>
<Chip <Chip
icon="information" title={task.status}
mode="outlined" icon={{
selectedColor={statusColors[task.status]} name: 'information',
style={[styles.chip, { backgroundColor: theme.colors.surface }]} type: 'material-community',
textStyle={{ fontSize: 12 }} size: 20,
> color: statusColors[task.status],
{task.status} }}
</Chip> type="outline"
</Card.Content> containerStyle={styles.chip}
</Card> titleStyle={{ color: statusColors[task.status], fontSize: 12 }}
buttonStyle={{ borderColor: statusColors[task.status] }}
/>
</Card>
</TouchableOpacity>
); );
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
card: { card: {
margin: 8, margin: 4,
width: '45%', borderRadius: 8,
}, },
title: { title: {
marginBottom: 12, marginBottom: 12,
minHeight: 50, // Ensure cards have similar height minHeight: 50, // Ensure cards have similar height
textAlign: 'left',
}, },
chip: { chip: {
alignSelf: 'flex-start', alignSelf: 'flex-start',

View File

@ -1,8 +1,13 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { View, StyleSheet, ScrollView } from 'react-native'; import { View, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { TextInput, Menu, Button, Divider, Text } from 'react-native-paper'; import { Input, BottomSheet, ListItem, Text } from '@rneui/themed';
import { Task, RobotAction } from '../types/task'; import { Task, RobotAction } from '../types/task';
import { LOCATIONS, ROBOT_ACTIONS, PAYLOADS, LOCATIONS_BAYS } from '../data/mockData'; import {
LOCATIONS,
ROBOT_ACTIONS,
PAYLOADS,
LOCATIONS_BAYS,
} from '../data/mockData';
interface TaskFormProps { interface TaskFormProps {
task: Task; task: Task;
@ -10,91 +15,117 @@ interface TaskFormProps {
} }
const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => { const TaskForm: React.FC<TaskFormProps> = ({ task, onTaskChange }) => {
const [menuVisible, setMenuVisible] = useState<{ [key: string]: boolean }>({}); const [isVisible, setIsVisible] = useState(false);
const [currentField, setCurrentField] = useState('');
const [currentItems, setCurrentItems] = useState<
{ label: string; value: string }[]
>([]);
const openMenu = (name: string) => setMenuVisible(prev => ({ ...prev, [name]: true })); const handleParamChange = (field: string, value: string | RobotAction) => {
const closeMenu = (name: string) => setMenuVisible(prev => ({ ...prev, [name]: false })); const updatedTask = {
...task,
const handleParamChange = (field: string, value: string | RobotAction) => { parameters: {
const updatedTask = { ...task.parameters,
...task, [field]: value,
parameters: { },
...task.parameters,
[field]: value,
},
};
onTaskChange(updatedTask);
}; };
onTaskChange(updatedTask);
};
const renderDropdown = ( const openBottomSheet = (
name: string, field: string,
label: string, items: { label: string; value: string }[],
value: string, ) => {
items: { label: string, value: string }[] setCurrentField(field);
) => ( setCurrentItems(items);
<View style={styles.input}> setIsVisible(true);
<Text>{label}</Text> };
<Menu
visible={menuVisible[name]} const renderDropdown = (
onDismiss={() => closeMenu(name)} name: string,
anchor={ label: string,
<Button onPress={() => openMenu(name)} mode="outlined"> value: string,
{value || `选择${label}`} items: { label: string; value: string }[],
</Button> ) => (
} <TouchableOpacity onPress={() => openBottomSheet(name, items)}>
<Input
label={label}
value={value}
editable={false}
rightIcon={{ name: 'arrow-drop-down' }}
/>
</TouchableOpacity>
);
return (
<ScrollView contentContainerStyle={styles.container}>
<Input
label="任务名称"
value={task.name}
onChangeText={text => onTaskChange({ ...task, name: text })}
/>
{renderDropdown(
'startLocation',
'起点',
task.parameters.startLocation,
LOCATIONS,
)}
{renderDropdown(
'endLocation',
'终点',
task.parameters.endLocation,
LOCATIONS,
)}
<Input
label="途经点 (可选)"
value={task.parameters.waypoint || ''}
onChangeText={text => handleParamChange('waypoint', text)}
/>
{renderDropdown(
'robotAction',
'机器人动作',
task.parameters.robotAction,
ROBOT_ACTIONS,
)}
{renderDropdown('payload', '载荷', task.parameters.payload, PAYLOADS)}
{renderDropdown(
'locationBay',
'库位',
task.parameters.locationBay,
LOCATIONS_BAYS,
)}
<BottomSheet
isVisible={isVisible}
onBackdropPress={() => setIsVisible(false)}
>
<ScrollView>
{currentItems.map((item, index) => (
<ListItem
key={index}
onPress={() => {
handleParamChange(currentField, item.value);
setIsVisible(false);
}}
> >
<ScrollView style={{ maxHeight: 200 }}> <ListItem.Content>
{items.map(item => ( <ListItem.Title>{item.label}</ListItem.Title>
<Menu.Item </ListItem.Content>
key={item.value} </ListItem>
onPress={() => { ))}
handleParamChange(name, item.value);
closeMenu(name);
}}
title={item.label}
/>
))}
</ScrollView>
</Menu>
</View>
);
return (
<ScrollView contentContainerStyle={styles.container}>
<TextInput
label="任务名称"
value={task.name}
onChangeText={text => onTaskChange({ ...task, name: text })}
style={styles.input}
mode="outlined"
/>
{renderDropdown('startLocation', '起点', task.parameters.startLocation, LOCATIONS)}
{renderDropdown('endLocation', '终点', task.parameters.endLocation, LOCATIONS)}
<TextInput
label="途经点 (可选)"
value={task.parameters.waypoint || ''}
onChangeText={text => handleParamChange('waypoint', text)}
style={styles.input}
mode="outlined"
/>
{renderDropdown('robotAction', '机器人动作', task.parameters.robotAction, ROBOT_ACTIONS)}
{renderDropdown('payload', '载荷', task.parameters.payload, PAYLOADS)}
{renderDropdown('locationBay', '库位', task.parameters.locationBay, LOCATIONS_BAYS)}
</ScrollView> </ScrollView>
); </BottomSheet>
</ScrollView>
);
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
padding: 16, padding: 16,
}, },
input: {
marginBottom: 16,
},
}); });
export default TaskForm; export default TaskForm;

View File

@ -6,7 +6,7 @@ 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 { ActivityIndicator } from 'react-native-paper'; import { Dialog } from '@rneui/themed';
type RootStackParamList = { type RootStackParamList = {
TaskEdit: { taskId: string }; TaskEdit: { taskId: string };
@ -67,7 +67,11 @@ export default function TaskEditScreen() {
} }
if (!task) { if (!task) {
return <ActivityIndicator animating={true} size="large" style={styles.loader}/>; return (
<Dialog isVisible={true}>
<Dialog.Loading />
</Dialog>
);
} }
return ( return (
@ -89,10 +93,6 @@ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
backgroundColor: '#fff', backgroundColor: '#fff',
paddingBottom: 60,
}, },
loader: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
}); });

View File

@ -38,6 +38,5 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
}, },
list: { list: {
padding: 8, paddingHorizontal: 8,
} }});
});