import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { ActivityIndicator, Alert, Pressable, ScrollView, StyleSheet, Text, View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useAIInterview } from '../src/hooks'; import { colors } from '../src/theme'; import type { InterviewAbilityImprovement, InterviewMode, InterviewModeOption, InterviewQuestionTypeCode, InterviewQuestionTypeItem, InterviewRecent, InterviewSpecial, } from '../src/types'; const COLOR_TOKENS: Record = { primary: colors.primary, success: colors.success, action: colors.action, info: colors.info, danger: colors.danger, }; /* ===================== 子组件:顶部导航 ===================== */ function Header({ onBack, onShare }: { onBack: () => void; onShare: () => void }) { return ( AI模拟面试 ); } /* ===================== 子组件:面试模式选择 ===================== */ function ModeCards({ modes, active, onSelect, }: { modes: InterviewModeOption[]; active: InterviewMode; onSelect: (mode: InterviewMode) => void; }) { return ( 面试模式 {modes.map((mode) => { const selected = mode.id === active; return ( onSelect(mode.id)} > {mode.label} {mode.desc} {selected && } ); })} ); } /* ===================== 子组件:岗位信息(两列网格) ===================== */ function JobInfoGrid({ items, }: { items: { label: string; value: string }[]; }) { return ( {items.map((item) => ( {item.label} {item.value} ))} ); } /* ===================== 子组件:题型设置 ===================== */ function QuestionTypeTags({ types, active, onToggle, }: { types: InterviewQuestionTypeItem[]; active: InterviewQuestionTypeCode[]; onToggle: (id: InterviewQuestionTypeCode) => void; }) { return ( 题型设置 {types.map((t) => { const selected = active.includes(t.id); const color = COLOR_TOKENS[t.color] ?? colors.primary; return ( onToggle(t.id)} > {selected && } {t.label} ); })} ); } /* ===================== 子组件:专项突破选项 ===================== */ function SpecialTags({ specials, active, onToggle, }: { specials: InterviewSpecial[]; active: string[]; onToggle: (id: string) => void; }) { return ( 专项突破 {specials.map((s) => { const selected = active.includes(s.id); return ( onToggle(s.id)} > {s.label} ); })} ); } /* ===================== 子组件:预估可提升能力 ===================== */ function ImprovementCard({ improvements, }: { improvements: InterviewAbilityImprovement[]; }) { return ( 本次预估可提升能力 {improvements.map((imp) => { const color = COLOR_TOKENS[imp.color] ?? colors.primary; return ( {imp.label} +{imp.percent}% ); })} ); } /* ===================== 子组件:最近练习 ===================== */ function RecentCard({ recent }: { recent: InterviewRecent[] }) { return ( {/* 原先这里挂着「全部记录 >」:项目内没有面试练习记录页,先摘掉 */} 最近练习 {recent.map((item) => { const color = COLOR_TOKENS[item.resultColor] ?? colors.primary; return ( {item.date} {item.title} {item.result} ); })} ); } /* ===================== 子组件:四态 ===================== */ function Loading() { return ( 正在加载面试配置… ); } function ErrorState({ onRetry }: { onRetry: () => void }) { return ( 配置加载失败 点击重试 ); } /* ===================== 页面 ===================== */ export default function InterviewScreen() { const router = useRouter(); const { data, loading, error, refresh, mode, setMode, questionTypes, toggleQuestionType, specials, toggleSpecial, start, starting, } = useAIInterview(); const handleStart = async () => { await start(); }; // 右上角「分享」暂无对应页面,按项目约定弹提示(此前绑定空函数,点击无任何反馈) const handleShare = () => { Alert.alert('提示', '分享面试配置功能开发中,敬请期待'); }; if (loading) { return (
router.back()} onShare={handleShare} /> ); } if (error || !data) { return (
router.back()} onShare={handleShare} /> ); } return (
router.back()} onShare={handleShare} /> {mode === 'special' && ( )} {starting ? ( ) : ( 开始面试 )} ); } 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: 40, alignItems: 'center' }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, body: { paddingHorizontal: 16, paddingBottom: 100, paddingTop: 12 }, 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 }, section: { marginBottom: 16 }, sectionTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary, marginBottom: 12 }, modeCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.white, borderRadius: 12, padding: 14, marginBottom: 10, borderWidth: 1, borderColor: colors.borderLight, }, modeCardActive: { borderColor: colors.primary, backgroundColor: colors.primaryLight }, modeIcon: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center', }, modeBody: { flex: 1, marginLeft: 12 }, modeLabel: { fontSize: 15, fontWeight: '700', color: colors.textPrimary }, modeLabelActive: { color: colors.primaryText }, modeDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 2 }, card: { backgroundColor: colors.white, borderRadius: 14, padding: 16, marginBottom: 12, borderWidth: 1, borderColor: colors.borderLight, }, cardHeaderRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12, }, cardTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary }, moreLink: { fontSize: 13, color: colors.primary }, jobGrid: { flexDirection: 'row', flexWrap: 'wrap' }, jobItem: { width: '50%', marginBottom: 14 }, jobLabel: { fontSize: 12, color: colors.textHint, marginBottom: 4 }, jobValue: { fontSize: 14, fontWeight: '700', color: colors.textPrimary }, tagRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 }, typeTag: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 8, borderRadius: 18, backgroundColor: colors.white, borderWidth: 1, borderColor: colors.border, }, typeText: { fontSize: 13, color: colors.textSecondary, marginLeft: 4 }, typeTextActive: { color: colors.white, fontWeight: '600' }, specialTag: { paddingHorizontal: 14, paddingVertical: 8, borderRadius: 18, backgroundColor: colors.pageBg, borderWidth: 1, borderColor: colors.borderLight, }, specialTagActive: { backgroundColor: colors.primaryLight, borderColor: colors.primary }, specialText: { fontSize: 13, color: colors.textSecondary }, specialTextActive: { color: colors.primaryText, fontWeight: '600' }, improvementRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 8, }, improvementLabel: { width: 70, fontSize: 13, color: colors.textPrimary }, improvementTrack: { flex: 1, height: 8, borderRadius: 4, backgroundColor: colors.borderLight, overflow: 'hidden', }, improvementFill: { height: 8, borderRadius: 4 }, improvementValue: { width: 50, fontSize: 13, fontWeight: '700', textAlign: 'right', }, recentRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10, borderTopWidth: 1, borderTopColor: colors.borderLight, }, recentDate: { width: 60, fontSize: 12, color: colors.textHint }, recentTitle: { flex: 1, fontSize: 14, color: colors.textPrimary }, recentTag: { borderRadius: 10, paddingHorizontal: 8, paddingVertical: 2 }, recentResult: { fontSize: 12, fontWeight: '600' }, footer: { position: 'absolute', left: 0, right: 0, bottom: 0, paddingHorizontal: 16, paddingVertical: 12, backgroundColor: colors.white, borderTopWidth: 1, borderTopColor: colors.borderLight, }, startBtn: { backgroundColor: colors.primaryBtn, borderRadius: 24, paddingVertical: 14, alignItems: 'center', }, startBtnDisabled: { backgroundColor: colors.textDisabled }, startBtnText: { fontSize: 16, fontWeight: '700', color: colors.white }, });