webapp/src/screens/LocationScreen.tsx

404 lines
10 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useState, useCallback } from 'react';
import {
StyleSheet,
View,
FlatList,
Text,
ActivityIndicator,
Alert,
RefreshControl,
} from 'react-native';
import { SearchBar, CheckBox, Button, Overlay, ListItem } from '@rneui/themed';
import api from '../services/api';
import { getConfig } from '../services/configService';
interface Location {
id: string;
layer_name: string;
is_occupied: boolean;
is_locked: boolean;
is_empty_tray: boolean;
is_disabled: boolean;
}
const LocationScreen = () => {
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 [isRefreshing, setIsRefreshing] = useState(false);
const fetchData = useCallback(async (query = '') => {
setLoading(true);
try {
const config = await getConfig();
const endpoint =
(config?.apiEndpoints as any)?.getLocationList ||
'/api/vwed-operate-point/list';
const response = await api.get(endpoint, {
params: { layer_name: query },
});
console.log('API Response:', response); // 调试日志
// API 拦截器已经处理了响应,直接使用返回的数据
const data = response as any;
if (data && data.storage_locations) {
setLocations(data.storage_locations);
} else if (Array.isArray(data)) {
setLocations(data);
} else {
console.warn('Unexpected response format:', data);
setLocations([]);
}
} catch (error) {
console.error(error);
Alert.alert('错误', '加载库位列表失败。');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
const handleSearch = () => {
fetchData(search);
};
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
try {
await fetchData(search);
} catch (error) {
console.error('刷新失败:', error);
} finally {
setIsRefreshing(false);
}
}, [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 config = await getConfig();
const endpoint =
(config?.apiEndpoints as any)?.batchUpdateLocation ||
'/api/vwed-operate-point/batch-status';
const response = await api.put(endpoint, {
layer_names: selectedLocations,
action: action,
});
console.log('Batch Update Response:', response); // 调试日志
// API 拦截器已经处理了响应,直接使用返回的数据
const { success_count, failed_count, results } = response as any;
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: any) {
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}>
<CheckBox
checked={selectedLocations.includes(item.id)}
onPress={() => toggleSelection(item.id)}
containerStyle={styles.checkboxContainer}
checkedColor="#4CAF50"
uncheckedColor="#666"
/>
<Text style={styles.itemText}>{item.layer_name}</Text>
<Text
style={[
styles.itemText,
{ color: item.is_occupied ? '#F44336' : '#4CAF50' },
]}
>
{item.is_occupied ? '是' : '否'}
</Text>
<Text
style={[
styles.itemText,
{ color: item.is_locked ? '#F44336' : '#4CAF50' },
]}
>
{item.is_locked ? '是' : '否'}
</Text>
<Text
style={[
styles.itemText,
{ color: item.is_empty_tray ? '#FF9800' : '#4CAF50' },
]}
>
{item.is_empty_tray ? '是' : '否'}
</Text>
<Text
style={[
styles.itemText,
{ color: item.is_disabled ? '#F44336' : '#4CAF50' },
]}
>
{item.is_disabled ? '是' : '否'}
</Text>
</View>
);
if (loading) {
return (
<View style={styles.loader}>
<ActivityIndicator size="large" color="#4CAF50" />
<Text style={styles.loadingText}>...</Text>
</View>
);
}
return (
<View style={styles.container}>
<View style={styles.searchContainer}>
<SearchBar
placeholder="搜索库位..."
onChangeText={setSearch}
value={search}
containerStyle={styles.searchBarContainer}
inputContainerStyle={styles.searchInputContainer}
inputStyle={styles.searchInput}
placeholderTextColor="#999"
searchIcon={{ color: '#999' }}
clearIcon={{ color: '#999' }}
/>
<Button
title="搜索"
onPress={handleSearch}
buttonStyle={styles.searchButton}
titleStyle={styles.searchButtonText}
/>
</View>
<Button
title="批量操作"
onPress={() => setOverlayVisible(true)}
containerStyle={styles.batchButtonContainer}
buttonStyle={styles.batchButton}
titleStyle={styles.batchButtonText}
/>
<Overlay
isVisible={isOverlayVisible}
onBackdropPress={() => setOverlayVisible(false)}
overlayStyle={styles.overlay}
>
<View style={styles.overlayContainer}>
<Text style={styles.overlayTitle}></Text>
{actions.map((item, index) => (
<ListItem
key={index}
onPress={() => handleBatchUpdate(item.action)}
bottomDivider
containerStyle={styles.listItemContainer}
>
<ListItem.Content>
<ListItem.Title style={styles.listItemTitle}>
{item.title}
</ListItem.Title>
</ListItem.Content>
</ListItem>
))}
</View>
</Overlay>
<View style={styles.headerContainer}>
<Text style={styles.headerText}></Text>
<Text style={styles.headerText}></Text>
<Text style={styles.headerText}></Text>
<Text style={styles.headerText}></Text>
<Text style={styles.headerText}></Text>
</View>
<FlatList
data={locations}
renderItem={renderItem}
keyExtractor={item => item.id}
ItemSeparatorComponent={() => <View style={styles.separator} />}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
colors={['#4CAF50']}
tintColor="#4CAF50"
title="下拉刷新"
titleColor="#999"
/>
}
showsVerticalScrollIndicator={false}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#1a1a1a',
},
loader: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#1a1a1a',
},
loadingText: {
color: '#999',
marginTop: 10,
fontSize: 16,
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#2a2a2a',
paddingHorizontal: 10,
paddingVertical: 5,
},
searchBarContainer: {
flex: 1,
backgroundColor: 'transparent',
borderTopWidth: 0,
borderBottomWidth: 0,
paddingHorizontal: 0,
},
searchInputContainer: {
backgroundColor: '#333',
borderRadius: 8,
},
searchInput: {
color: '#fff',
fontSize: 16,
},
searchButton: {
backgroundColor: '#4CAF50',
borderRadius: 8,
paddingHorizontal: 20,
paddingVertical: 12,
marginLeft: 10,
},
searchButtonText: {
color: '#fff',
fontWeight: 'bold',
},
batchButtonContainer: {
margin: 15,
},
batchButton: {
backgroundColor: '#2196F3',
borderRadius: 8,
paddingVertical: 12,
},
batchButtonText: {
color: '#fff',
fontWeight: 'bold',
fontSize: 16,
},
overlay: {
backgroundColor: '#2a2a2a',
borderRadius: 12,
padding: 0,
},
overlayContainer: {
minWidth: 250,
paddingVertical: 10,
},
overlayTitle: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
paddingVertical: 15,
borderBottomWidth: 1,
borderBottomColor: '#444',
},
listItemContainer: {
backgroundColor: 'transparent',
paddingVertical: 15,
paddingHorizontal: 20,
borderBottomColor: '#444',
},
listItemTitle: {
fontSize: 16,
textAlign: 'center',
color: '#fff',
},
itemContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 12,
paddingHorizontal: 10,
backgroundColor: '#2a2a2a',
},
checkboxContainer: {
backgroundColor: 'transparent',
borderWidth: 0,
padding: 0,
marginLeft: 0,
marginRight: 5,
},
itemText: {
flex: 1,
textAlign: 'center',
fontSize: 14,
color: '#fff',
},
headerContainer: {
flexDirection: 'row',
padding: 12,
backgroundColor: '#333',
borderBottomWidth: 1,
borderBottomColor: '#444',
},
headerText: {
flex: 1,
textAlign: 'center',
fontWeight: 'bold',
fontSize: 16,
color: '#fff',
},
separator: {
height: 1,
backgroundColor: '#333',
},
});
export default LocationScreen;