✨ feat: 完整应用重构 - 优化界面架构与用户体验
主要变更: - 重构应用界面:优化首页、登录、认证流程 - 新增用户档案模块:包含完整的档案管理组件 - 优化路由结构:重新组织页面布局和导航 - 改进API集成:新增活动、内容分类等API模块 - 删除冗余组件:清理不必要的文件和依赖 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Image as ExpoImage } from 'expo-image';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { memo } from 'react';
|
||||
import {
|
||||
FlatList,
|
||||
ListRenderItemInfo,
|
||||
Pressable,
|
||||
Image as RNImage,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
import { Image } from 'expo-image';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
|
||||
export type CommunityItem = {
|
||||
id: string;
|
||||
@@ -26,22 +28,43 @@ type CommunityGridProps = {
|
||||
onPressAction?: (item: CommunityItem) => void;
|
||||
};
|
||||
|
||||
const EMPTY_ITEM: CommunityItem = {
|
||||
id: 'empty',
|
||||
title: '',
|
||||
image: '',
|
||||
chip: '',
|
||||
actionLabel: '',
|
||||
};
|
||||
|
||||
export function CommunityGrid({ items, onPressCard, onPressAction }: CommunityGridProps) {
|
||||
const renderItem = ({ item, index }: ListRenderItemInfo<CommunityItem>) => {
|
||||
const isEmpty = item.id === 'empty';
|
||||
const isLeftColumn = index % 2 === 0;
|
||||
|
||||
return (
|
||||
<CommunityCard
|
||||
item={item}
|
||||
onPressCard={onPressCard}
|
||||
onPressAction={onPressAction}
|
||||
style={[styles.cardWrapper, isLeftColumn ? styles.cardLeft : styles.cardRight]}
|
||||
/>
|
||||
<View style={[styles.cardWrapper, isLeftColumn ? styles.cardLeft : styles.cardRight]}>
|
||||
{isEmpty ? (
|
||||
<View style={styles.emptyCard} />
|
||||
) : (
|
||||
<CommunityCard
|
||||
item={item}
|
||||
onPressCard={onPressCard}
|
||||
onPressAction={onPressAction}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const data = items.length === 0
|
||||
? [EMPTY_ITEM, EMPTY_ITEM]
|
||||
: items.length === 1
|
||||
? [...items, EMPTY_ITEM]
|
||||
: items.slice(0, 4);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={items}
|
||||
data={data}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item) => item.id}
|
||||
numColumns={2}
|
||||
@@ -62,11 +85,15 @@ type CommunityCardProps = {
|
||||
const CommunityCard = memo(({ item, style, onPressCard, onPressAction }: CommunityCardProps) => {
|
||||
return (
|
||||
<Pressable onPress={() => onPressCard?.(item)} style={[styles.card, style]}>
|
||||
<View>
|
||||
<Image source={{ uri: item.image }} style={styles.cardImage} contentFit="cover" />
|
||||
<View style={styles.imageWrapper}>
|
||||
<ExpoImage source={{ uri: item.image }} style={styles.cardImage} contentFit="cover" />
|
||||
<LinearGradient
|
||||
colors={['rgba(12, 14, 20, 0)', 'rgba(12, 14, 20, 0.85)']}
|
||||
style={styles.imageGradient}
|
||||
/>
|
||||
<Chip label={item.chip} />
|
||||
<Text style={styles.cardTitle}>{item.title}</Text>
|
||||
</View>
|
||||
<Text style={styles.cardTitle}>{item.title}</Text>
|
||||
<ActionButton label={item.actionLabel} onPress={() => onPressAction?.(item)} />
|
||||
</Pressable>
|
||||
);
|
||||
@@ -80,8 +107,8 @@ type ChipProps = {
|
||||
const Chip = memo(({ label }: ChipProps) => {
|
||||
return (
|
||||
<View style={styles.chip}>
|
||||
<Ionicons name="people-outline" size={14} color="#E5E9F2" style={styles.chipIcon} />
|
||||
<Text style={styles.chipText}>{label}</Text>
|
||||
<Ionicons name="people-outline" size={14} color="#F4F7FF" style={styles.chipIcon} />
|
||||
</View>
|
||||
);
|
||||
});
|
||||
@@ -95,7 +122,11 @@ type ActionButtonProps = {
|
||||
const ActionButton = memo(({ label, onPress }: ActionButtonProps) => {
|
||||
return (
|
||||
<Pressable onPress={onPress} style={({ pressed }) => [styles.button, pressed && styles.buttonPressed]}>
|
||||
<Ionicons name="flash-outline" size={16} color="#050505" style={styles.buttonIcon} />
|
||||
<RNImage
|
||||
source={require('@/assets/icons/start.svg')}
|
||||
style={styles.buttonIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<Text style={styles.buttonText}>{label}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
@@ -119,61 +150,84 @@ const styles = StyleSheet.create({
|
||||
cardRight: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#101115',
|
||||
emptyCard: {
|
||||
backgroundColor: 'transparent',
|
||||
borderRadius: 20,
|
||||
padding: 14,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#2E3031',
|
||||
borderRadius: 20,
|
||||
padding: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: '#1C1D22',
|
||||
borderColor: '#1B1D24',
|
||||
},
|
||||
imageWrapper: {
|
||||
position: 'relative',
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
marginBottom: 0,
|
||||
},
|
||||
cardImage: {
|
||||
width: '100%',
|
||||
height: 148,
|
||||
borderRadius: 16,
|
||||
marginBottom: 14,
|
||||
aspectRatio: 9 / 16,
|
||||
height: undefined,
|
||||
},
|
||||
imageGradient: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: 88,
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 13,
|
||||
letterSpacing: 0.8,
|
||||
position: 'absolute',
|
||||
left: 16,
|
||||
bottom: 18,
|
||||
fontSize: 14,
|
||||
letterSpacing: 1,
|
||||
fontWeight: '700',
|
||||
color: '#F5F8FF',
|
||||
marginBottom: 12,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
chip: {
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
right: 12,
|
||||
backgroundColor: 'rgba(5, 5, 5, 0.72)',
|
||||
borderRadius: 14,
|
||||
top: 14,
|
||||
right: 14,
|
||||
backgroundColor: 'rgba(27, 43, 78, 0.92)',
|
||||
borderRadius: 16,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 4,
|
||||
paddingVertical: 5,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
chipIcon: {
|
||||
marginRight: 6,
|
||||
marginLeft: 6,
|
||||
},
|
||||
chipText: {
|
||||
color: '#C7FF00',
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
color: '#E4EBFF',
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
button: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 10,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#B7FF2F',
|
||||
paddingVertical: 14,
|
||||
borderRadius: 16,
|
||||
borderWidth: 0,
|
||||
},
|
||||
buttonPressed: {
|
||||
opacity: 0.9,
|
||||
opacity: 0.85,
|
||||
},
|
||||
buttonIcon: {
|
||||
marginRight: 4,
|
||||
width: 18,
|
||||
height: 18,
|
||||
marginRight: 8,
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
color: '#050505',
|
||||
color: '#C7FF00',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { memo } from 'react';
|
||||
import { Dimensions, FlatList, ListRenderItem, Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import { Image } from 'expo-image';
|
||||
|
||||
const WINDOW_WIDTH = Dimensions.get('window').width;
|
||||
const CARD_HORIZONTAL_GUTTER = 18;
|
||||
const CARD_WIDTH = Math.min(WINDOW_WIDTH - 64, 360);
|
||||
const CARD_WIDTH = Math.min(WINDOW_WIDTH - 124, 360);
|
||||
|
||||
export type FeatureItem = {
|
||||
id: string;
|
||||
@@ -20,7 +20,7 @@ type FeatureCarouselProps = {
|
||||
|
||||
export function FeatureCarousel({ items, onPress }: FeatureCarouselProps) {
|
||||
const cardInterval = CARD_WIDTH + CARD_HORIZONTAL_GUTTER;
|
||||
|
||||
|
||||
const renderItem: ListRenderItem<FeatureItem> = ({ item }) => <FeatureCard item={item} onPress={onPress} />;
|
||||
|
||||
return (
|
||||
@@ -54,7 +54,9 @@ const FeatureCard = memo(({ item, onPress }: FeatureCardProps) => {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Image source={{ uri: item.image }} style={styles.image} contentFit="cover" />
|
||||
<View style={styles.imageContainer}>
|
||||
<Image source={{ uri: item.image }} style={styles.image} contentFit="cover" />
|
||||
</View>
|
||||
<View style={styles.cardContent}>
|
||||
<Text style={styles.cardTitle}>{item.title}</Text>
|
||||
<Text style={styles.cardSubtitle}>{item.subtitle}</Text>
|
||||
@@ -66,36 +68,47 @@ FeatureCard.displayName = 'FeatureCard';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
paddingVertical: 18,
|
||||
paddingVertical: 12,
|
||||
paddingRight: 24,
|
||||
},
|
||||
card: {
|
||||
width: CARD_WIDTH,
|
||||
marginRight: CARD_HORIZONTAL_GUTTER,
|
||||
borderRadius: 22,
|
||||
overflow: 'hidden',
|
||||
borderRadius: 24,
|
||||
padding: 0,
|
||||
paddingBottom: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: '#1B1C20',
|
||||
backgroundColor: '#0F1013',
|
||||
shadowColor: '#000000',
|
||||
shadowOpacity: 0.28,
|
||||
shadowRadius: 18,
|
||||
shadowOffset: { width: 0, height: 12 },
|
||||
elevation: 12,
|
||||
},
|
||||
imageContainer: {
|
||||
borderRadius: 18,
|
||||
overflow: 'hidden',
|
||||
marginBottom: 16,
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: 184,
|
||||
},
|
||||
cardContent: {
|
||||
padding: 18,
|
||||
backgroundColor: '#111115',
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
color: '#F4F8FF',
|
||||
textTransform: 'uppercase',
|
||||
marginBottom: 6,
|
||||
letterSpacing: 0.8,
|
||||
letterSpacing: 1.2,
|
||||
},
|
||||
cardSubtitle: {
|
||||
fontSize: 13,
|
||||
color: '#9AA0AD',
|
||||
color: '#B4BBC5',
|
||||
lineHeight: 18,
|
||||
},
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user