import { Ionicons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { useRouter } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { useRef, useState } from 'react'; import { Pressable, ScrollView, StyleSheet, Text, useWindowDimensions, View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import {colors, onboardingGradient, spacing} from '../src/theme'; import { useAuth } from '../src/context/AuthContext'; import type { AssetName } from '../components/onboarding/assets'; import { ImageSlot } from '../components/onboarding/ui/ImageSlot'; interface GuideSlide { /** 屏内插图素材位(见 components/onboarding/assets.ts) */ art: AssetName; title: string; desc: string; } /** * 引导页三屏。标题是产品定的文案,副标题只写**已上线**的模块 * (AI 简历优化 / AI 模拟面试 / AI 合同咨询、霍兰德测评、成长页文章与闯关), * 不写还没做的能力。 * * 整页用品牌主色渐变打底(见 src/theme/colors.ts 的 onboardingGradient), * 所以文字统一用 textInverse 反白,按钮反过来用白底 + primaryText 蓝字。 * * 每屏配图走 ImageSlot —— 素材未到位时页面上会显示「配图待补 + 该放什么图」的标注。 */ const SLIDES: GuideSlide[] = [ { art: 'guideAiCopilot', title: '求职开挂 AI护驾', desc: 'AI 简历优化、AI 模拟面试、AI 合同咨询,从投递到签约都有人接话。', }, { art: 'guideAbilityTest', title: '真实能力 一测便知', desc: '霍兰德兴趣测评 + 能力自评,先看清自己的底牌,再决定投什么岗。', }, { art: 'guideGrowthUnlock', title: '一起解锁 职场秘籍', desc: '成长页的职场文章与闯关任务,把该准备的提前准备好。', }, ]; /** * 引导页(/guide)。 * * 两种进入方式,出口不同 —— 出口错了会把已登录用户甩回注册流程: * - 首次启动:`/` 判定没看过引导时跳来,看完落 /register; * - 已登录后回顾:设置页「查看引导页」推入,出口是返回上一页,不能再去 /register。 * * 底部按钮前两屏是「下一步」(翻页),最后一屏才是「开启体验」(离开引导页)。 * 三屏都直接离开的话,后两屏等于摆设。 */ export default function GuideScreen() { const router = useRouter(); const { completeOnboarding, isTokenValid } = useAuth(); const { width } = useWindowDimensions(); const [page, setPage] = useState(0); const scrollRef = useRef(null); const isLast = page === SLIDES.length - 1; /** 已登录(含从设置页回顾)时只返回,不改引导标记、不进注册流程 */ const reviewing = isTokenValid; const finish = () => { if (reviewing) { if (router.canGoBack()) router.back(); else router.replace('/(tabs)/jobs'); return; } completeOnboarding(); router.replace('/register'); }; const goNext = () => { if (isLast) { finish(); return; } const next = page + 1; setPage(next); scrollRef.current?.scrollTo({ x: next * width, animated: true }); }; const slotWidth = Math.min(width - 96, 300); const slotHeight = Math.round(slotWidth * 0.72); return ( {/* 整页深蓝底,状态栏要走浅色图标,否则黑字压蓝底看不清 */} {reviewing ? ( ) : ( )} { const index = Math.round(e.nativeEvent.contentOffset.x / width); setPage(index); }} style={styles.pager} > {SLIDES.map((slide) => ( {slide.title} {slide.desc} ))} {SLIDES.map((slide, i) => ( ))} {isLast ? (reviewing ? '完成' : '开启体验') : '下一步'} ); } const styles = StyleSheet.create({ container: { flex: 1 }, safe: { flex: 1 }, topBar: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: spacing.framework, paddingVertical: 10, }, backBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center', }, pager: { flex: 1 }, slide: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 32, }, slideTitle: { fontSize: 26, fontWeight: '800', color: colors.textInverse, textAlign: 'center', letterSpacing: 1, marginTop: 32, marginBottom: 12, }, slideDesc: { fontSize: 15, lineHeight: 24, color: colors.textInverse, textAlign: 'center', }, bottom: { alignItems: 'center', paddingHorizontal: spacing.framework, paddingBottom: 28, }, dots: { flexDirection: 'row', gap: 8, marginBottom: 24 }, dot: { width: 8, height: 8, borderRadius: 4, backgroundColor: colors.onBrandTrack, }, dotActive: { width: 22, backgroundColor: colors.textInverse }, nextBtn: { width: '100%', height: 50, borderRadius: 25, backgroundColor: colors.white, alignItems: 'center', justifyContent: 'center', }, nextText: { fontSize: 16, fontWeight: '700', color: colors.primaryText }, });