webapp/src/screens/LocationScreen.tsx

219 lines
6.7 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useState, useCallback } from 'react';
import { StyleSheet, View, FlatList, Text, ActivityIndicator, Alert } from 'react-native';
import { SearchBar, CheckBox, Button, useTheme, Overlay, ListItem } from '@rneui/themed';
import api from '../services/api';
interface Location {
id: string;
layer_name: string;
is_occupied: boolean;
is_locked: boolean;
is_empty_tray: boolean;
is_disabled: boolean;
}
const LocationScreen = () => {
const { theme } = useTheme();
const [locations, setLocations] = useState<Location[]>([]);
const [search, setSearch] = useState('');
const [loading, setLoading] = useState(true);
const [selectedLocations, setSelectedLocations] = useState<string[]>([]);
const [isOverlayVisible, setOverlayVisible] = useState(false);
const fetchData = useCallback(async (query = '') => {
setLoading(true);
try {
const response = await api.get('/api/vwed-operate-point/list', {
params: { layer_name: query },
});
setLocations(response.storage_locations);
} catch (error) {
console.error(error);
Alert.alert('错误', '加载库位列表失败。');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
const handleSearch = () => {
fetchData(search);
};
const toggleSelection = (id: string) => {
setSelectedLocations(prev =>
prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]
);
};
const handleBatchUpdate = async (action: string) => {
if (selectedLocations.length === 0) {
Alert.alert('未选择', '请选择要操作的库位。');
return;
}
setOverlayVisible(false);
try {
const response = await api.put('/api/vwed-operate-point/batch-status', {
layer_names: selectedLocations,
action: action,
});
const { success_count, failed_count, results } = response;
let message = `批量操作完成:\n成功 ${success_count} 个, 失败 ${failed_count} 个。\n\n`;
if (failed_count > 0) {
message += '失败详情:\n';
results.forEach((res: any) => {
if (!res.success) {
message += `${res.layer_name}: ${res.message}\n`;
}
});
}
Alert.alert('操作结果', message);
fetchData(search);
setSelectedLocations([]);
} catch (error) {
console.error(error);
Alert.alert('错误', `批量操作失败: ${error.message}`);
}
};
const actions = [
{ title: '禁用', action: 'disable' },
{ title: '启用', action: 'enable' },
{ title: '占用', action: 'occupy' },
{ title: '释放', action: 'release' },
{ title: '锁定', action: 'lock' },
{ title: '解锁', action: 'unlock' },
];
const renderItem = ({ item }: { item: Location }) => (
<View style={[styles.itemContainer, { backgroundColor: theme.colors.background }]}>
<CheckBox
checked={selectedLocations.includes(item.id)}
onPress={() => toggleSelection(item.id)}
containerStyle={{ backgroundColor: 'transparent' }}
/>
<Text style={[styles.itemText, { color: theme.colors.black }]}>{item.layer_name}</Text>
<Text style={[styles.itemText, { color: item.is_occupied ? 'red' : 'green' }]}>{item.is_occupied ? '是' : '否'}</Text>
<Text style={[styles.itemText, { color: item.is_locked ? 'red' : 'green' }]}>{item.is_locked ? '是' : '否'}</Text>
<Text style={[styles.itemText, { color: item.is_empty_tray ? 'orange' : 'green' }]}>{item.is_empty_tray ? '是' : '否'}</Text>
<Text style={[styles.itemText, { color: item.is_disabled ? 'red' : 'green' }]}>{item.is_disabled ? '是' : '否'}</Text>
</View>
);
if (loading) {
return (
<View style={styles.loader}>
<ActivityIndicator size="large" color={theme.colors.primary} />
</View>
);
}
return (
<View style={[styles.container, { backgroundColor: theme.colors.grey5 }]}>
<View style={styles.searchContainer}>
<SearchBar
placeholder="搜索库位..."
onChangeText={setSearch}
value={search}
containerStyle={styles.searchBarContainer}
inputContainerStyle={{ backgroundColor: theme.colors.grey4 }}
/>
<Button title="搜索" onPress={handleSearch} buttonStyle={styles.searchButton} />
</View>
<Button
title="批量操作"
onPress={() => setOverlayVisible(true)}
containerStyle={styles.batchButtonContainer}
buttonStyle={{ backgroundColor: theme.colors.primary }}
/>
<Overlay isVisible={isOverlayVisible} onBackdropPress={() => setOverlayVisible(false)}>
<View>
{actions.map((item, index) => (
<ListItem key={index} onPress={() => handleBatchUpdate(item.action)} bottomDivider>
<ListItem.Content>
<ListItem.Title>{item.title}</ListItem.Title>
</ListItem.Content>
</ListItem>
))}
</View>
</Overlay>
<View style={[styles.headerContainer, { backgroundColor: theme.colors.grey4 }]}>
<Text style={[styles.headerText, { color: theme.colors.black }]}></Text>
<Text style={[styles.headerText, { color: theme.colors.black }]}></Text>
<Text style={[styles.headerText, { color: theme.colors.black }]}></Text>
<Text style={[styles.headerText, { color: theme.colors.black }]}></Text>
<Text style={[styles.headerText, { color: theme.colors.black }]}></Text>
</View>
<FlatList
data={locations}
renderItem={renderItem}
keyExtractor={(item) => item.id}
ItemSeparatorComponent={() => <View style={styles.separator} />}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
loader: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
},
searchBarContainer: {
flex: 1,
backgroundColor: 'transparent',
borderTopWidth: 0,
borderBottomWidth: 0,
},
searchButton: {
marginRight: 10,
},
batchButtonContainer: {
margin: 10,
},
itemContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
paddingHorizontal: 10,
},
itemText: {
flex: 1,
textAlign: 'center',
fontSize: 14,
},
headerContainer: {
flexDirection: 'row',
padding: 12,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
headerText: {
flex: 1,
textAlign: 'center',
fontWeight: 'bold',
fontSize: 16,
},
separator: {
height: 1,
backgroundColor: '#e0e0e0',
},
});
export default LocationScreen;