|
@@ -1,7 +1,7 @@
|
|
|
import { Barrage } from '@/components/Barrage';
|
|
import { Barrage } from '@/components/Barrage';
|
|
|
import { Image } from 'expo-image';
|
|
import { Image } from 'expo-image';
|
|
|
import { useRouter } from 'expo-router';
|
|
import { useRouter } from 'expo-router';
|
|
|
-import React, { useCallback, useEffect, useState } from 'react';
|
|
|
|
|
|
|
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
import {
|
|
import {
|
|
|
ActivityIndicator,
|
|
ActivityIndicator,
|
|
|
FlatList,
|
|
FlatList,
|
|
@@ -38,6 +38,83 @@ interface BarrageItem {
|
|
|
poolId?: string;
|
|
poolId?: string;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Static Header Component - Memoized to prevent re-renders
|
|
|
|
|
+const StaticHeader = React.memo(({ barrageList }: { barrageList: BarrageItem[] }) => (
|
|
|
|
|
+ <View>
|
|
|
|
|
+ {/* 占位空间 - 给顶部搜索栏留出空间 */}
|
|
|
|
|
+ <View style={{ height: 53 }} />
|
|
|
|
|
+
|
|
|
|
|
+ {/* 顶部主图 - 绝对定位,叠在背景上 */}
|
|
|
|
|
+ <View style={styles.mainImageContainer}>
|
|
|
|
|
+ <Image
|
|
|
|
|
+ source={{ uri: Images.box.awardMainImg }}
|
|
|
|
|
+ style={styles.mainImage}
|
|
|
|
|
+ contentFit="fill"
|
|
|
|
|
+ />
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 占位空间 - 主图高度 */}
|
|
|
|
|
+ <View style={{ height: 360 }} />
|
|
|
|
|
+
|
|
|
|
|
+ {/* 弹幕区域 */}
|
|
|
|
|
+ {barrageList && barrageList.length > 0 && (
|
|
|
|
|
+ <View style={styles.barrageSection}>
|
|
|
|
|
+ <Barrage data={barrageList.slice(0, Math.ceil(barrageList.length / 2))} />
|
|
|
|
|
+ <View style={{ height: 6 }} />
|
|
|
|
|
+ <Barrage
|
|
|
|
|
+ data={barrageList.slice(Math.ceil(barrageList.length / 2))}
|
|
|
|
|
+ speed={35}
|
|
|
|
|
+ />
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </View>
|
|
|
|
|
+));
|
|
|
|
|
+
|
|
|
|
|
+// Type Selector Component
|
|
|
|
|
+const TypeSelector = React.memo(({
|
|
|
|
|
+ typeIndex,
|
|
|
|
|
+ priceSort,
|
|
|
|
|
+ onTypeChange,
|
|
|
|
|
+ onSortChange
|
|
|
|
|
+}: {
|
|
|
|
|
+ typeIndex: number;
|
|
|
|
|
+ priceSort: number;
|
|
|
|
|
+ onTypeChange: (index: number) => void;
|
|
|
|
|
+ onSortChange: () => void;
|
|
|
|
|
+}) => (
|
|
|
|
|
+ <View style={styles.typeSection}>
|
|
|
|
|
+ <View style={styles.typeList}>
|
|
|
|
|
+ {typeList.map((item, index) => (
|
|
|
|
|
+ <TouchableOpacity
|
|
|
|
|
+ key={index}
|
|
|
|
|
+ style={styles.typeItem}
|
|
|
|
|
+ onPress={() => onTypeChange(index)}
|
|
|
|
|
+ activeOpacity={0.7}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Image
|
|
|
|
|
+ source={{ uri: typeIndex === index ? item.imgOn : item.img }}
|
|
|
|
|
+ style={styles.typeImage}
|
|
|
|
|
+ contentFit="contain"
|
|
|
|
|
+ />
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <TouchableOpacity style={styles.sortBtn} onPress={onSortChange} activeOpacity={0.7}>
|
|
|
|
|
+ <Image
|
|
|
|
|
+ source={{
|
|
|
|
|
+ uri: priceSort === 0
|
|
|
|
|
+ ? Images.box.sortAmount
|
|
|
|
|
+ : priceSort === 1
|
|
|
|
|
+ ? Images.box.sortAmountOnT
|
|
|
|
|
+ : Images.box.sortAmountOnB,
|
|
|
|
|
+ }}
|
|
|
|
|
+ style={styles.sortIcon}
|
|
|
|
|
+ contentFit="contain"
|
|
|
|
|
+ />
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ </View>
|
|
|
|
|
+));
|
|
|
|
|
+
|
|
|
export default function BoxScreen() {
|
|
export default function BoxScreen() {
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
const insets = useSafeAreaInsets();
|
|
const insets = useSafeAreaInsets();
|
|
@@ -124,18 +201,18 @@ export default function BoxScreen() {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const handleTypeChange = (index: number) => {
|
|
|
|
|
|
|
+ const handleTypeChange = useCallback((index: number) => {
|
|
|
setTypeIndex(index);
|
|
setTypeIndex(index);
|
|
|
setList([]);
|
|
setList([]);
|
|
|
setCurrent(1);
|
|
setCurrent(1);
|
|
|
setHasMore(true);
|
|
setHasMore(true);
|
|
|
- };
|
|
|
|
|
|
|
+ }, []);
|
|
|
|
|
|
|
|
- const handlePriceSort = () => {
|
|
|
|
|
|
|
+ const handlePriceSort = useCallback(() => {
|
|
|
setPriceSort((prev) => (prev + 1) % 3);
|
|
setPriceSort((prev) => (prev + 1) % 3);
|
|
|
- };
|
|
|
|
|
|
|
+ }, []);
|
|
|
|
|
|
|
|
- const handleItemPress = (item: PoolItem) => {
|
|
|
|
|
|
|
+ const handleItemPress = useCallback((item: PoolItem) => {
|
|
|
// 检查商品状态
|
|
// 检查商品状态
|
|
|
if (item.status !== undefined && item.status !== 1) return;
|
|
if (item.status !== undefined && item.status !== 1) return;
|
|
|
|
|
|
|
@@ -155,9 +232,9 @@ export default function BoxScreen() {
|
|
|
// 其他商品
|
|
// 其他商品
|
|
|
router.push(`/product/${item.id}` as any);
|
|
router.push(`/product/${item.id}` as any);
|
|
|
}
|
|
}
|
|
|
- };
|
|
|
|
|
|
|
+ }, [router]);
|
|
|
|
|
|
|
|
- const renderItem = ({ item }: { item: PoolItem }) => (
|
|
|
|
|
|
|
+ const renderItem = useCallback(({ item }: { item: PoolItem }) => (
|
|
|
<TouchableOpacity
|
|
<TouchableOpacity
|
|
|
style={styles.itemContainer}
|
|
style={styles.itemContainer}
|
|
|
onPress={() => handleItemPress(item)}
|
|
onPress={() => handleItemPress(item)}
|
|
@@ -171,7 +248,7 @@ export default function BoxScreen() {
|
|
|
<Image
|
|
<Image
|
|
|
source={{ uri: item.cover }}
|
|
source={{ uri: item.cover }}
|
|
|
style={styles.itemImage}
|
|
style={styles.itemImage}
|
|
|
- contentFit="cover" // Changed back to 'cover' to fill height/width
|
|
|
|
|
|
|
+ contentFit="cover"
|
|
|
/>
|
|
/>
|
|
|
<View style={styles.itemInfo}>
|
|
<View style={styles.itemInfo}>
|
|
|
<Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
|
|
<Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
|
|
@@ -182,75 +259,21 @@ export default function BoxScreen() {
|
|
|
</View>
|
|
</View>
|
|
|
</ImageBackground>
|
|
</ImageBackground>
|
|
|
</TouchableOpacity>
|
|
</TouchableOpacity>
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const renderHeader = () => (
|
|
|
|
|
- <View>
|
|
|
|
|
- {/* 占位空间 - 给顶部搜索栏留出空间 */}
|
|
|
|
|
- <View style={{ height: 53 }} />
|
|
|
|
|
-
|
|
|
|
|
- {/* 顶部主图 - 绝对定位,叠在背景上 */}
|
|
|
|
|
- <View style={styles.mainImageContainer}>
|
|
|
|
|
- <Image
|
|
|
|
|
- source={{ uri: Images.box.awardMainImg }}
|
|
|
|
|
- style={styles.mainImage}
|
|
|
|
|
- contentFit="fill"
|
|
|
|
|
- />
|
|
|
|
|
- </View>
|
|
|
|
|
|
|
+ ), [handleItemPress]);
|
|
|
|
|
|
|
|
- {/* 占位空间 - 主图高度 */}
|
|
|
|
|
- <View style={{ height: 360 }} />
|
|
|
|
|
-
|
|
|
|
|
- {/* 弹幕区域 */}
|
|
|
|
|
- {barrageList && barrageList.length > 0 && (
|
|
|
|
|
- <View style={styles.barrageSection}>
|
|
|
|
|
- <Barrage data={barrageList.slice(0, Math.ceil(barrageList.length / 2))} />
|
|
|
|
|
- <View style={{ height: 6 }} />
|
|
|
|
|
- <Barrage
|
|
|
|
|
- data={barrageList.slice(Math.ceil(barrageList.length / 2))}
|
|
|
|
|
- speed={35}
|
|
|
|
|
|
|
+ const ListHeader = useMemo(() => (
|
|
|
|
|
+ <View>
|
|
|
|
|
+ <StaticHeader barrageList={barrageList} />
|
|
|
|
|
+ <TypeSelector
|
|
|
|
|
+ typeIndex={typeIndex}
|
|
|
|
|
+ priceSort={priceSort}
|
|
|
|
|
+ onTypeChange={handleTypeChange}
|
|
|
|
|
+ onSortChange={handlePriceSort}
|
|
|
/>
|
|
/>
|
|
|
- </View>
|
|
|
|
|
- )}
|
|
|
|
|
- </View>
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // 分类筛选单独渲染
|
|
|
|
|
- const renderTypeSection = () => (
|
|
|
|
|
- <View style={styles.typeSection}>
|
|
|
|
|
- <View style={styles.typeList}>
|
|
|
|
|
- {typeList.map((item, index) => (
|
|
|
|
|
- <TouchableOpacity
|
|
|
|
|
- key={index}
|
|
|
|
|
- style={styles.typeItem}
|
|
|
|
|
- onPress={() => handleTypeChange(index)}
|
|
|
|
|
- activeOpacity={0.7}
|
|
|
|
|
- >
|
|
|
|
|
- <Image
|
|
|
|
|
- source={{ uri: typeIndex === index ? item.imgOn : item.img }}
|
|
|
|
|
- style={styles.typeImage}
|
|
|
|
|
- contentFit="contain"
|
|
|
|
|
- />
|
|
|
|
|
- </TouchableOpacity>
|
|
|
|
|
- ))}
|
|
|
|
|
</View>
|
|
</View>
|
|
|
- <TouchableOpacity style={styles.sortBtn} onPress={handlePriceSort} activeOpacity={0.7}>
|
|
|
|
|
- <Image
|
|
|
|
|
- source={{
|
|
|
|
|
- uri: priceSort === 0
|
|
|
|
|
- ? Images.box.sortAmount
|
|
|
|
|
- : priceSort === 1
|
|
|
|
|
- ? Images.box.sortAmountOnT
|
|
|
|
|
- : Images.box.sortAmountOnB,
|
|
|
|
|
- }}
|
|
|
|
|
- style={styles.sortIcon}
|
|
|
|
|
- contentFit="contain"
|
|
|
|
|
- />
|
|
|
|
|
- </TouchableOpacity>
|
|
|
|
|
- </View>
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ ), [barrageList, typeIndex, priceSort, handleTypeChange, handlePriceSort]);
|
|
|
|
|
|
|
|
- const renderFooter = () => {
|
|
|
|
|
|
|
+ const renderFooter = useCallback(() => {
|
|
|
if (!loading) return null;
|
|
if (!loading) return null;
|
|
|
return (
|
|
return (
|
|
|
<View style={styles.footer}>
|
|
<View style={styles.footer}>
|
|
@@ -258,16 +281,16 @@ export default function BoxScreen() {
|
|
|
<Text style={styles.footerText}>加载中...</Text>
|
|
<Text style={styles.footerText}>加载中...</Text>
|
|
|
</View>
|
|
</View>
|
|
|
);
|
|
);
|
|
|
- };
|
|
|
|
|
|
|
+ }, [loading]);
|
|
|
|
|
|
|
|
- const renderEmpty = () => {
|
|
|
|
|
|
|
+ const renderEmpty = useCallback(() => {
|
|
|
if (loading) return null;
|
|
if (loading) return null;
|
|
|
return (
|
|
return (
|
|
|
<View style={styles.empty}>
|
|
<View style={styles.empty}>
|
|
|
<Text style={styles.emptyText}>暂无数据</Text>
|
|
<Text style={styles.emptyText}>暂无数据</Text>
|
|
|
</View>
|
|
</View>
|
|
|
);
|
|
);
|
|
|
- };
|
|
|
|
|
|
|
+ }, [loading]);
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<View style={styles.container}>
|
|
<View style={styles.container}>
|
|
@@ -312,12 +335,7 @@ export default function BoxScreen() {
|
|
|
data={list}
|
|
data={list}
|
|
|
renderItem={renderItem}
|
|
renderItem={renderItem}
|
|
|
keyExtractor={(item) => item.id}
|
|
keyExtractor={(item) => item.id}
|
|
|
- ListHeaderComponent={() => (
|
|
|
|
|
- <>
|
|
|
|
|
- {renderHeader()}
|
|
|
|
|
- {renderTypeSection()}
|
|
|
|
|
- </>
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ ListHeaderComponent={ListHeader}
|
|
|
ListFooterComponent={renderFooter}
|
|
ListFooterComponent={renderFooter}
|
|
|
ListEmptyComponent={renderEmpty}
|
|
ListEmptyComponent={renderEmpty}
|
|
|
contentContainerStyle={styles.listContent}
|
|
contentContainerStyle={styles.listContent}
|