import { useRouter } from 'expo-router'; import { ScrollView, StyleSheet, Text, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { PrimaryButton } from '../../components/onboarding/ui/PrimaryButton'; import { CenterState, EditHeader, PageFooter } from '../../components/resume/EditKit'; import { useResume } from '../../src/hooks'; import { UNSET_LABEL } from '../../src/constants'; import { colors } from '../../src/theme'; import type { Resume, ResumeEducation, ResumeExperience, ResumeProject } from '../../src/types'; /** * 简历预览页(只读)。 * * 只渲染简历**正文**:不含评分卡与优化建议 —— 那些是 App 内的辅助信息,不会出现在 * 导出的文件里,混在一起会让人以为导出物里也有。 * * 空板块**保留标题 + 「未填写 X」**:预览的价值正是让用户看见自己缺什么, * 整段隐去反而看不出漏了哪一块。 */ export default function ResumePreviewScreen() { const router = useRouter(); const { resume, loading, error, refresh } = useResume(); const header = router.back()} />; if (resume === null) { return ( {header} {error !== null ? ( ) : ( )} ); } const { basicInfo, jobIntention, education, experience, projects, skills, selfEvaluation } = resume; return ( {header} {/* 纸张:白底 + 描边,与导出效果同一套排版 */} {/* TODO(素材):接入后端后这里渲染用户上传的头像图片; 当前以姓名首字兜底,无姓名时不占位 */} {basicInfo.avatar !== '' ? basicInfo.avatar : '简'} {basicInfo.name !== '' ? basicInfo.name : UNSET_LABEL} {/* 有值才渲染:空白行比不渲染更让人困惑 */} {[basicInfo.phone, basicInfo.email, basicInfo.location].filter((v) => v !== '').length > 0 && ( {[basicInfo.phone, basicInfo.email, basicInfo.location].filter((v) => v !== '').join(' · ')} )}
{hasIntention(jobIntention) ? ( {[ jobIntention.position, jobIntention.city, jobIntention.salary, jobIntention.statusLabel, ] .filter((v) => v !== '') .join(' · ')} ) : ( )}
{education.length === 0 ? ( ) : ( education.map((edu) => ) )}
{experience.length === 0 ? ( ) : ( experience.map((exp) => ) )}
{projects.length === 0 ? ( ) : ( projects.map((proj) => ) )}
{skills.length === 0 ? ( ) : ( {skills.map((s) => s.name).join(' · ')} )}
{selfEvaluation.content === '' ? ( ) : ( {selfEvaluation.content} )}
预览为简历正文效果,不含评分与优化建议。模板样式、纸张大小需后端导出服务支持,当前按默认版式呈现。
router.push('/resume/export')} />
); } /* ===================== 子组件 ===================== */ function Section({ title, children }: { title: string; children: React.ReactNode }) { return ( {title} {children} ); } function Empty({ text }: { text: string }) { return {text}; } function EducationItem({ edu }: { edu: ResumeEducation }) { // 专业 / 学历 / 年份可能为空,拼出来会剩半截值 const majorDegree = [edu.major, edu.degree].filter(Boolean).join(' · '); const years = [edu.startYear, edu.endYear].filter(Boolean).join('-'); return ( {edu.school} {years !== '' && {years}} {majorDegree !== '' && {majorDegree}} ); } function ExperienceItem({ exp }: { exp: ResumeExperience }) { const dates = [exp.startDate, exp.endDate].filter(Boolean).join(' - '); return ( {exp.company} {dates !== '' && {dates}} {exp.position !== '' && {exp.position}} {exp.description !== '' && {exp.description}} ); } function ProjectItem({ proj }: { proj: ResumeProject }) { return ( {proj.name} {proj.role !== '' && {proj.role}} {proj.description !== '' && {proj.description}} ); } /** 求职意向四项全空才算未填写(引导只采集到其中一两项时也算有内容) */ function hasIntention(intention: Resume['jobIntention']): boolean { return ( [intention.position, intention.city, intention.salary, intention.statusLabel].filter( (v) => v !== '', ).length > 0 ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.pageBg }, body: { padding: 8, paddingBottom: 24 }, paper: { backgroundColor: colors.white, borderRadius: 8, padding: 20, borderWidth: 1, borderColor: colors.borderLight, }, paperHead: { flexDirection: 'row', alignItems: 'center', marginBottom: 20 }, avatar: { width: 52, height: 52, borderRadius: 26, backgroundColor: colors.primary, alignItems: 'center', justifyContent: 'center', }, avatarText: { fontSize: 22, fontWeight: '700', color: colors.white }, headBody: { marginLeft: 14, flex: 1 }, name: { fontSize: 20, fontWeight: '800', color: colors.textPrimary }, contact: { fontSize: 13, color: colors.textSecondary, marginTop: 6 }, section: { marginBottom: 18 }, sectionTitle: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, borderLeftWidth: 3, borderLeftColor: colors.primary, paddingLeft: 8, marginBottom: 10, }, item: { marginBottom: 12 }, itemHead: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }, itemTitle: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, flex: 1 }, itemMeta: { fontSize: 12, color: colors.textHint }, itemSub: { fontSize: 13, color: colors.textSecondary, marginTop: 4 }, paragraph: { fontSize: 13, color: colors.textSecondary, lineHeight: 21, marginTop: 6 }, emptyText: { fontSize: 13, color: colors.textHint }, tip: { fontSize: 12, color: colors.textHint, lineHeight: 18, marginTop: 4 }, });