404 lines
10 KiB
TypeScript
404 lines
10 KiB
TypeScript
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;
|