boxList.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { Image } from 'expo-image';
  2. import { useRouter } from 'expo-router';
  3. import React, { useEffect, useState } from 'react';
  4. import {
  5. ActivityIndicator,
  6. Alert,
  7. ScrollView,
  8. StatusBar,
  9. StyleSheet,
  10. Text,
  11. TouchableOpacity,
  12. View,
  13. } from 'react-native';
  14. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  15. import { get, post } from '@/services/http';
  16. interface BoxItem {
  17. id: string;
  18. boxName: string;
  19. boxCover: string;
  20. }
  21. const getMyBoxes = async () => {
  22. const res = await get('/api/nestedBox/myBoxes');
  23. return res.data;
  24. };
  25. const openBoxes = async (boxIds: string) => {
  26. const res = await post('/api/nestedBox/openBox', { boxIds });
  27. return res;
  28. };
  29. export default function BoxListScreen() {
  30. const router = useRouter();
  31. const insets = useSafeAreaInsets();
  32. const [loading, setLoading] = useState(true);
  33. const [list, setList] = useState<BoxItem[]>([]);
  34. const [selectedIds, setSelectedIds] = useState<string[]>([]);
  35. const loadData = async () => {
  36. setLoading(true);
  37. try {
  38. const res = await getMyBoxes();
  39. setList(res || []);
  40. } catch (error) {
  41. console.error('加载宝箱列表失败:', error);
  42. }
  43. setLoading(false);
  44. };
  45. useEffect(() => {
  46. loadData();
  47. }, []);
  48. const toggleSelect = (id: string) => {
  49. setSelectedIds(prev =>
  50. prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
  51. );
  52. };
  53. const selectAll = () => {
  54. if (selectedIds.length === list.length) {
  55. setSelectedIds([]);
  56. } else {
  57. setSelectedIds(list.map(item => item.id));
  58. }
  59. };
  60. const handleOpen = async () => {
  61. if (selectedIds.length === 0) {
  62. Alert.alert('提示', '请先选择宝箱');
  63. return;
  64. }
  65. try {
  66. const res = await openBoxes(selectedIds.join(','));
  67. if (res.code === 0) {
  68. Alert.alert('成功', '开启成功');
  69. setSelectedIds([]);
  70. loadData();
  71. }
  72. } catch (error) {
  73. console.error('开启失败:', error);
  74. }
  75. };
  76. return (
  77. <View style={styles.container}>
  78. <StatusBar barStyle="light-content" />
  79. <View style={[styles.header, { paddingTop: insets.top }]}>
  80. <TouchableOpacity onPress={() => router.back()}>
  81. <Text style={styles.backText}>←</Text>
  82. </TouchableOpacity>
  83. <Text style={styles.title}>宝箱</Text>
  84. <View style={{ width: 40 }} />
  85. </View>
  86. {loading ? (
  87. <ActivityIndicator size="large" color="#fff" style={{ marginTop: 50 }} />
  88. ) : (
  89. <ScrollView style={styles.content}>
  90. <View style={styles.grid}>
  91. {list.map(item => (
  92. <TouchableOpacity
  93. key={item.id}
  94. style={[styles.item, selectedIds.includes(item.id) && styles.itemSelected]}
  95. onPress={() => toggleSelect(item.id)}
  96. >
  97. <Image source={{ uri: item.boxCover }} style={styles.itemImage} contentFit="cover" />
  98. <Text style={styles.itemName} numberOfLines={1}>{item.boxName}</Text>
  99. {selectedIds.includes(item.id) && (
  100. <View style={styles.checkMark}><Text style={styles.checkText}>✓</Text></View>
  101. )}
  102. </TouchableOpacity>
  103. ))}
  104. </View>
  105. </ScrollView>
  106. )}
  107. <View style={[styles.footer, { paddingBottom: insets.bottom + 10 }]}>
  108. <TouchableOpacity style={styles.selectAllBtn} onPress={selectAll}>
  109. <Text style={styles.btnText}>{selectedIds.length === list.length ? '取消全选' : '全选'}</Text>
  110. </TouchableOpacity>
  111. <TouchableOpacity style={styles.openBtn} onPress={handleOpen}>
  112. <Text style={styles.btnText}>开启宝箱 {selectedIds.length > 0 ? `(${selectedIds.length})` : ''}</Text>
  113. </TouchableOpacity>
  114. </View>
  115. </View>
  116. );
  117. }
  118. const styles = StyleSheet.create({
  119. container: { flex: 1, backgroundColor: '#1a1a2e' },
  120. header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 15, paddingBottom: 10 },
  121. backText: { color: '#fff', fontSize: 24 },
  122. title: { color: '#fff', fontSize: 18, fontWeight: 'bold' },
  123. content: { flex: 1, padding: 10 },
  124. grid: { flexDirection: 'row', flexWrap: 'wrap' },
  125. item: { width: '33%', padding: 5, position: 'relative' },
  126. itemSelected: { opacity: 0.7 },
  127. itemImage: { width: '100%', aspectRatio: 1, borderRadius: 8 },
  128. itemName: { color: '#fff', fontSize: 12, textAlign: 'center', marginTop: 5 },
  129. checkMark: { position: 'absolute', top: 10, right: 10, backgroundColor: '#ff6600', width: 24, height: 24, borderRadius: 12, justifyContent: 'center', alignItems: 'center' },
  130. checkText: { color: '#fff', fontSize: 14 },
  131. footer: { flexDirection: 'row', paddingHorizontal: 15, paddingTop: 10, backgroundColor: 'rgba(0,0,0,0.8)' },
  132. selectAllBtn: { flex: 1, backgroundColor: '#666', paddingVertical: 12, borderRadius: 8, marginRight: 10, alignItems: 'center' },
  133. openBtn: { flex: 2, backgroundColor: '#ff6600', paddingVertical: 12, borderRadius: 8, alignItems: 'center' },
  134. btnText: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
  135. });