/** * 简历编辑页共用件(基本信息 / 教育背景 / 工作经历 / 项目经历四页同一套骨架)。 * * 抽出来的理由:四页的头部、表单行、四态、页脚完全一致,逐页复制会让 * 「错误提示样式」「禁用态按钮」这类细节在四处各自漂移。 */ import { Ionicons } from '@expo/vector-icons'; import type { ReactNode } from 'react'; import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; import { colors } from '../../src/theme'; /** 顶部导航:返回 + 居中标题(右侧留同宽占位,保证标题居中) */ export function EditHeader({ title, onBack }: { title: string; onBack: () => void }) { return ( {title} ); } /** * 表单行。`error` 只在**填了但填错**时传入 —— 空值是「未采集」不是「填错了」, * 一进页面就飘红字会让人以为自己做错了什么。 */ export function Field({ label, value, onChangeText, placeholder, keyboardType, maxLength, error, hint, multiline, autoFocus, required, }: { label: string; value: string; onChangeText: (v: string) => void; placeholder: string; keyboardType?: 'default' | 'phone-pad' | 'email-address' | 'number-pad'; maxLength?: number; error?: string; hint?: string; multiline?: boolean; autoFocus?: boolean; /** 必填项在 label 后打一个红色星号(只做视觉标注,拦截规则在页面里) */ required?: boolean; }) { const hasError = error !== undefined && error !== ''; return ( {label} {required === true && *} {hasError && {error}} {!hasError && hint !== undefined && hint !== '' && {hint}} ); } /** 只读说明行(放在卡片或页脚里,字号小于正文) */ export function FormNote({ text }: { text: string }) { return {text}; } /** 白色圆角卡片容器(与简历首页的卡片同款) */ export function SectionCard({ title, right, children }: { title: string; right?: ReactNode; children: ReactNode }) { return ( {title} {right} {children} ); } /** 页脚容器:上方一条浅描边,主按钮靠它压底 */ export function PageFooter({ children }: { children: ReactNode }) { return {children}; } /** 居中状态块(加载中 / 加载失败 / 空列表都走这一个) */ export function CenterState({ icon, text, actionLabel, onAction, }: { icon?: keyof typeof Ionicons.glyphMap; text: string; actionLabel?: string; onAction?: () => void; }) { return ( {icon !== undefined && } {text} {actionLabel !== undefined && onAction !== undefined && ( {actionLabel} )} ); } /** 列表行右侧的图标按钮(编辑 / 删除) */ export function IconAction({ icon, onPress, color, label, }: { icon: keyof typeof Ionicons.glyphMap; onPress: () => void; color?: string; /** 无障碍标签:图标按钮对读屏用户是没有名字的,必须补一个 */ label: string; }) { return ( ); } const styles = StyleSheet.create({ header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 12, paddingVertical: 12, backgroundColor: colors.white, }, headerBtn: { width: 32, height: 32, alignItems: 'center', justifyContent: 'center' }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, section: { marginBottom: 18 }, label: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginBottom: 10 }, requiredMark: { color: colors.danger }, input: { height: 50, borderRadius: 12, borderWidth: 1, borderColor: colors.border, backgroundColor: colors.white, paddingHorizontal: 14, fontSize: 16, color: colors.textPrimary, }, inputMultiline: { height: 120, paddingTop: 12, textAlignVertical: 'top' }, inputError: { borderColor: colors.dangerBorder }, fieldError: { fontSize: 12, color: colors.dangerText, marginTop: 6 }, hint: { fontSize: 12, color: colors.textHint, marginTop: 6 }, note: { fontSize: 12, color: colors.textHint, lineHeight: 18 }, card: { backgroundColor: colors.white, borderRadius: 8, padding: 16, marginBottom: 12, borderWidth: 1, borderColor: colors.borderLight, }, cardHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10, }, cardTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary }, footer: { paddingHorizontal: 20, paddingVertical: 14, backgroundColor: colors.white, borderTopWidth: 1, borderTopColor: colors.borderLight, }, center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12, paddingVertical: 40 }, centerText: { fontSize: 14, color: colors.textSecondary }, centerBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight, }, centerBtnText: { fontSize: 13, fontWeight: '600', color: colors.primaryText }, iconBtn: { width: 32, height: 32, alignItems: 'center', justifyContent: 'center' }, });