You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
9.5 KiB
340 lines
9.5 KiB
import { Ionicons } from '@expo/vector-icons';
|
|
import { useRouter } from 'expo-router';
|
|
import {
|
|
ActivityIndicator,
|
|
Pressable,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
View,
|
|
} from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { useHollandIntro } from '../src/hooks';
|
|
import { colors, hollandTypeColors } from '../src/theme';
|
|
import { formatThousands } from '../src/utils';
|
|
import type { HollandDimensionIntro, HollandIntro } from '../src/types';
|
|
|
|
/* ===================== 子组件:顶部导航 ===================== */
|
|
|
|
function Header({ onBack }: { onBack: () => void }) {
|
|
return (
|
|
<View style={styles.header}>
|
|
<Pressable style={styles.headerBtn} onPress={onBack} hitSlop={8}>
|
|
<Ionicons name="chevron-back" size={24} color={colors.textPrimary} />
|
|
</Pressable>
|
|
<Text style={styles.headerTitle}>职业兴趣测评</Text>
|
|
{/* 原先这里是「记录」按钮:项目内没有测评记录页,留着等于点了没反应,先摘掉 */}
|
|
<View style={styles.headerBtn} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:六维类型网格 ===================== */
|
|
|
|
function DimensionGrid({ dimensions }: { dimensions: HollandDimensionIntro[] }) {
|
|
return (
|
|
<View style={styles.dimGrid}>
|
|
{dimensions.map((d) => (
|
|
<View key={d.type} style={styles.dimItem}>
|
|
<View style={[styles.dimBadge, { backgroundColor: hollandTypeColors[d.type] + '15' }]}>
|
|
<Text style={[styles.dimType, { color: hollandTypeColors[d.type] }]}>{d.type}</Text>
|
|
</View>
|
|
<Text style={styles.dimName}>{d.name}</Text>
|
|
<Text style={styles.dimDesc}>{d.desc}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:亮点卡片 ===================== */
|
|
|
|
function FeatureCard({
|
|
feature,
|
|
}: {
|
|
feature: HollandIntro['features'][number];
|
|
}) {
|
|
return (
|
|
<View style={styles.featureCard}>
|
|
<View style={styles.featureIcon}>
|
|
<Ionicons name={feature.icon as keyof typeof Ionicons.glyphMap} size={22} color={colors.primary} />
|
|
</View>
|
|
<View style={styles.featureBody}>
|
|
<Text style={styles.featureTitle}>{feature.title}</Text>
|
|
<Text style={styles.featureDesc}>{feature.desc}</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:优势列表 ===================== */
|
|
|
|
function AdvantageList({ advantages }: { advantages: string[] }) {
|
|
return (
|
|
<View style={styles.advantageCard}>
|
|
<Text style={styles.advantageTitle}>测评优势</Text>
|
|
{advantages.map((item, index) => (
|
|
<View key={index} style={styles.advantageRow}>
|
|
<Ionicons name="checkmark-circle" size={18} color={colors.success} />
|
|
<Text style={styles.advantageText}>{item}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 页面 ===================== */
|
|
|
|
export default function HollandIntroScreen() {
|
|
const router = useRouter();
|
|
const { intro, loading, error, refresh } = useHollandIntro();
|
|
|
|
if (loading) {
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top']}>
|
|
<Header onBack={() => router.back()} />
|
|
<View style={styles.center}>
|
|
<ActivityIndicator size="large" color={colors.primary} />
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
if (error || !intro) {
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top']}>
|
|
<Header onBack={() => router.back()} />
|
|
<View style={styles.center}>
|
|
<Ionicons name="help-circle-outline" size={40} color={colors.textHint} />
|
|
<Text style={styles.tip}>测评加载失败</Text>
|
|
<Pressable style={styles.retryBtn} onPress={refresh} hitSlop={6}>
|
|
<Text style={styles.retryText}>点击重试</Text>
|
|
</Pressable>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
|
<Header onBack={() => router.back()} />
|
|
<ScrollView contentContainerStyle={styles.body} showsVerticalScrollIndicator={false}>
|
|
{/* 标题区 */}
|
|
<View style={styles.heroCard}>
|
|
<View style={styles.heroIconWrap}>
|
|
<Ionicons name="shapes-outline" size={56} color={colors.primary} />
|
|
</View>
|
|
<Text style={styles.heroTitle}>{intro.title}</Text>
|
|
<Text style={styles.heroSubtitle}>{intro.subtitle}</Text>
|
|
{/* 与成长页的「已有 N 人完成」是同一个字段、同一句话,两处必须同一种格式化:
|
|
原先这里用 toLocaleString()、那边用 formatThousands(),而 Hermes 在无完整 ICU
|
|
的构建上 toLocaleString() 可能不加千分位,两页就会显示成两个样子。 */}
|
|
<Text style={styles.heroParticipants}>已有 {formatThousands(intro.totalParticipants)} 人完成测评</Text>
|
|
</View>
|
|
|
|
{/* 六维类型 */}
|
|
<Text style={styles.sectionTitle}>6大兴趣维度</Text>
|
|
<DimensionGrid dimensions={intro.dimensions} />
|
|
|
|
{/* 核心亮点 */}
|
|
<Text style={styles.sectionTitle}>核心亮点</Text>
|
|
<View style={styles.featureList}>
|
|
{intro.features.map((feature) => (
|
|
<FeatureCard key={feature.id} feature={feature} />
|
|
))}
|
|
</View>
|
|
|
|
{/* 测评优势 */}
|
|
<AdvantageList advantages={intro.advantages} />
|
|
|
|
<View style={styles.bottomSpacer} />
|
|
</ScrollView>
|
|
|
|
{/* 底部按钮 */}
|
|
<View style={styles.footer}>
|
|
<Pressable style={styles.startBtn} onPress={() => router.push('/holland/question')}>
|
|
<Text style={styles.startBtnText}>开始测评</Text>
|
|
</Pressable>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: colors.pageBg },
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 12,
|
|
backgroundColor: colors.white,
|
|
},
|
|
headerBtn: { width: 50, alignItems: 'center' },
|
|
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
|
|
headerRecord: { fontSize: 14, color: colors.primary, fontWeight: '500' },
|
|
body: { padding: 8, paddingBottom: 24 },
|
|
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
|
|
tip: { fontSize: 14, color: colors.textSecondary },
|
|
retryBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight },
|
|
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
|
|
heroCard: {
|
|
backgroundColor: colors.white,
|
|
borderRadius: 8,
|
|
padding: 8,
|
|
alignItems: 'center',
|
|
marginBottom: 8,
|
|
},
|
|
heroIconWrap: {
|
|
width: 96,
|
|
height: 96,
|
|
borderRadius: 48,
|
|
backgroundColor: colors.primaryLight,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginBottom: 16,
|
|
},
|
|
heroTitle: {
|
|
fontSize: 20,
|
|
fontWeight: '800',
|
|
color: colors.textPrimary,
|
|
marginBottom: 8,
|
|
textAlign: 'center',
|
|
},
|
|
heroSubtitle: {
|
|
fontSize: 13,
|
|
color: colors.textSecondary,
|
|
textAlign: 'center',
|
|
lineHeight: 20,
|
|
marginBottom: 12,
|
|
},
|
|
heroParticipants: {
|
|
fontSize: 12,
|
|
color: colors.textHint,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 16,
|
|
fontWeight: '700',
|
|
color: colors.textPrimary,
|
|
marginBottom: 12,
|
|
marginTop: 4,
|
|
},
|
|
dimGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 10,
|
|
marginBottom: 20,
|
|
},
|
|
dimItem: {
|
|
width: '30.8%',
|
|
backgroundColor: colors.white,
|
|
borderRadius: 8,
|
|
padding: 8,
|
|
alignItems: 'center',
|
|
},
|
|
dimBadge: {
|
|
width: 36,
|
|
height: 36,
|
|
borderRadius: 18,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginBottom: 8,
|
|
},
|
|
dimType: {
|
|
fontSize: 16,
|
|
fontWeight: '800',
|
|
},
|
|
dimName: {
|
|
fontSize: 14,
|
|
fontWeight: '700',
|
|
color: colors.textPrimary,
|
|
marginBottom: 4,
|
|
},
|
|
dimDesc: {
|
|
fontSize: 11,
|
|
color: colors.textSecondary,
|
|
textAlign: 'center',
|
|
lineHeight: 16,
|
|
},
|
|
featureList: {
|
|
gap: 8,
|
|
marginBottom: 20,
|
|
},
|
|
featureCard: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
backgroundColor: colors.white,
|
|
borderRadius: 8,
|
|
padding: 8,
|
|
},
|
|
featureIcon: {
|
|
width: 44,
|
|
height: 44,
|
|
borderRadius: 22,
|
|
backgroundColor: colors.primaryLight,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginRight: 14,
|
|
},
|
|
featureBody: {
|
|
flex: 1,
|
|
},
|
|
featureTitle: {
|
|
fontSize: 15,
|
|
fontWeight: '700',
|
|
color: colors.textPrimary,
|
|
marginBottom: 4,
|
|
},
|
|
featureDesc: {
|
|
fontSize: 12,
|
|
color: colors.textSecondary,
|
|
lineHeight: 18,
|
|
},
|
|
advantageCard: {
|
|
backgroundColor: colors.white,
|
|
borderRadius: 8,
|
|
padding: 8,
|
|
},
|
|
advantageTitle: {
|
|
fontSize: 15,
|
|
fontWeight: '700',
|
|
color: colors.textPrimary,
|
|
marginBottom: 12,
|
|
},
|
|
advantageRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: 10,
|
|
},
|
|
advantageText: {
|
|
fontSize: 13,
|
|
color: colors.textPrimary,
|
|
marginLeft: 10,
|
|
},
|
|
bottomSpacer: {
|
|
height: 80,
|
|
},
|
|
footer: {
|
|
position: 'absolute',
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
backgroundColor: colors.white,
|
|
borderTopWidth: 1,
|
|
borderTopColor: colors.borderLight,
|
|
paddingHorizontal: 16,
|
|
paddingTop: 12,
|
|
paddingBottom: 24,
|
|
},
|
|
startBtn: {
|
|
backgroundColor: colors.primaryBtn,
|
|
borderRadius: 12,
|
|
paddingVertical: 14,
|
|
alignItems: 'center',
|
|
},
|
|
startBtnText: {
|
|
color: colors.white,
|
|
fontSize: 16,
|
|
fontWeight: '700',
|
|
},
|
|
});
|
|
|