diff --git a/app/(enterprise)/enterprise-certification.tsx b/app/(enterprise)/enterprise-certification.tsx index 6e96c69..2d1f136 100644 --- a/app/(enterprise)/enterprise-certification.tsx +++ b/app/(enterprise)/enterprise-certification.tsx @@ -12,6 +12,7 @@ import { } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useEnterpriseCertification } from '../../src/hooks'; +import { useAuth } from '../../src/context/AuthContext'; import { colors } from '../../src/theme'; import type { EnterpriseCertificationForm, EnterpriseCertificationStatus } from '../../src/types'; @@ -88,6 +89,7 @@ function Field({ export default function EnterpriseCertificationScreen() { const router = useRouter(); + const { completeEnterpriseVerify } = useAuth(); const { certification, loading, error, refresh, submitting, submit, status } = useEnterpriseCertification(); const [form, setForm] = useState({ companyName: '', @@ -132,6 +134,15 @@ export default function EnterpriseCertificationScreen() { const handleSubmit = async () => { if (!form.companyName || !form.creditCode || !form.contactName || !form.contactPhone) return; await submit(form); + // 提交即视为走完「企业认证」必经步骤,标记后进入企业端(审核中也可浏览) + completeEnterpriseVerify(); + router.replace('/(enterprise)/enterprise-overview'); + }; + + const handleSkip = () => { + // 允许稍后认证:标记步骤已完成,先进企业端 + completeEnterpriseVerify(); + router.replace('/(enterprise)/enterprise-overview'); }; return ( @@ -166,7 +177,7 @@ export default function EnterpriseCertificationScreen() { 企业认证已通过 您已进入企业端,可发布岗位并管理招聘流程 - router.push('/enterprise-overview')}> + router.replace('/(enterprise)/enterprise-overview')}> 进入企业端 @@ -217,6 +228,13 @@ export default function EnterpriseCertificationScreen() { > {submitting ? '提交中…' : '提交认证'} + + 稍后认证 + )} diff --git a/app/identity-select.tsx b/app/identity-select.tsx index b2abf25..090cfe3 100644 --- a/app/identity-select.tsx +++ b/app/identity-select.tsx @@ -12,6 +12,7 @@ import { import { SafeAreaView } from 'react-native-safe-area-context'; import { useIdentityOptions } from '../src/hooks'; import { useRole } from '../src/context/RoleContext'; +import { useAuth } from '../src/context/AuthContext'; import { colors } from '../src/theme'; import type { IdentityOption } from '../src/types'; @@ -99,17 +100,27 @@ function ErrorState({ onRetry }: { onRetry: () => void }) { export default function IdentitySelectScreen() { const router = useRouter(); const { setRole } = useRole(); + const { candidateSetupDone, enterpriseVerified } = useAuth(); const { options, loading, error, refresh } = useIdentityOptions(); const [selectedId, setSelectedId] = useState(null); - const handleCandidatePress = () => { - setRole('candidate'); - router.replace('/(tabs)'); - }; - - const handleEnterprisePress = () => { - setRole('enterprise'); - router.replace('/(enterprise)'); + /** + * 选中身份即进入:先写 role,再进入该身份的下一步(避免点到卡片无反应)。 + * - 求职者:未设置求职意向 → 求职意向页;已设置 → 求职端 + * - 招聘者:未完成企业认证 → 企业认证页;已完成 → 企业端 + * 注意:两个路由组均无 index,故一律用显式落地路由。 + */ + const handleSelect = (option: IdentityOption) => { + setSelectedId(option.id); + if (option.role === 'candidate') { + setRole('candidate'); + router.replace(candidateSetupDone ? '/(tabs)/jobs' : '/job-preference'); + } else { + setRole('enterprise'); + router.replace( + enterpriseVerified ? '/(enterprise)/enterprise-overview' : '/enterprise-certification', + ); + } }; return ( @@ -129,27 +140,12 @@ export default function IdentitySelectScreen() { key={opt.id} option={opt} selected={opt.id === selectedId} - onSelect={() => setSelectedId(opt.id)} + onSelect={() => handleSelect(opt)} /> ))} - - - 加入求职端 - - - 进入企业端 - - + 点击上方身份卡片,立即进入对应端 )} @@ -201,15 +197,12 @@ const styles = StyleSheet.create({ cardTitleSelected: { color: colors.primaryText }, cardDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 6, lineHeight: 18 }, check: { position: 'absolute', top: 12, right: 12 }, - actions: { gap: 12 }, - actionBtn: { - borderRadius: 14, - paddingVertical: 14, - alignItems: 'center', + hint: { + marginTop: 8, + fontSize: 13, + color: colors.textHint, + textAlign: 'center', }, - candidateBtn: { backgroundColor: colors.primaryBtn }, - enterpriseBtn: { backgroundColor: colors.successBtn }, - actionText: { fontSize: 16, fontWeight: '700', color: colors.textInverse }, manageLink: { flexDirection: 'row', alignItems: 'center', diff --git a/app/index.tsx b/app/index.tsx index 51be2f1..478c387 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -19,5 +19,10 @@ export default function Index() { if (!role) { return ; } - return ; + // 两个路由组均无 index,必须用显式落地路由,否则落地页空白 + return ( + + ); } diff --git a/app/job-preference.tsx b/app/job-preference.tsx new file mode 100644 index 0000000..3b984d4 --- /dev/null +++ b/app/job-preference.tsx @@ -0,0 +1,251 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { useEffect, useState } from 'react'; +import { + ActivityIndicator, + Pressable, + ScrollView, + StyleSheet, + Text, + TextInput, + View, +} from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { colors } from '../src/theme'; +import { useAuth } from '../src/context/AuthContext'; +import { useJobPreference } from '../src/hooks'; +import type { JobPreference } from '../src/types'; + +/** 各维度可选项(用于岗位推荐匹配) */ +const CITIES = ['北京', '上海', '广州', '深圳', '杭州', '成都', '武汉', '南京', '西安', '苏州']; +const SALARIES = ['不限', '5-10K', '10-20K', '20-30K', '30-50K', '50K以上']; +const WORK_TYPES = ['全职', '兼职', '实习', '远程']; +const STATUSES = ['积极求职中', '观望好机会', '在职暂不考虑']; + +/* ===================== 子组件:单选项 chips ===================== */ + +function ChipGroup({ + title, + options, + value, + onSelect, +}: { + title: string; + options: string[]; + value: string; + onSelect: (v: string) => void; +}) { + return ( + + {title} + + {options.map((opt) => { + const selected = opt === value; + return ( + onSelect(opt)} + > + {opt} + + ); + })} + + + ); +} + +/* ===================== 子组件:四态 ===================== */ + +function Loading() { + return ( + + + 正在加载求职意向… + + ); +} + +function ErrorState({ onRetry }: { onRetry: () => void }) { + return ( + + + 加载失败 + + 点击重试 + + + ); +} + +/* ===================== 页面 ===================== */ + +export default function JobPreferenceScreen() { + const router = useRouter(); + const { completeCandidateSetup } = useAuth(); + const { preference, loading, error, refresh, save } = useJobPreference(); + const [form, setForm] = useState(null); + const [saving, setSaving] = useState(false); + + useEffect(() => { + if (preference && !form) setForm(preference); + }, [preference, form]); + + if (loading || !form) { + return ( + + + + ); + } + + if (error) { + return ( + + + + ); + } + + const canSave = form.position.trim().length > 0; + + const update = (field: K, value: JobPreference[K]) => { + setForm((prev) => (prev ? { ...prev, [field]: value } : prev)); + }; + + const handleSave = async () => { + if (!canSave || saving) return; + setSaving(true); + try { + await save(form); + completeCandidateSetup(); + router.replace('/(tabs)/jobs'); + } finally { + setSaving(false); + } + }; + + const handleSkip = () => { + router.replace('/(tabs)/jobs'); + }; + + return ( + + + + 设置求职意向 + 告诉我们你的期望,才能推荐更匹配的岗位 + + + + 期望职位 + update('position', v)} + placeholder="如:前端开发工程师" + placeholderTextColor={colors.textHint} + /> + + + update('city', v)} + /> + update('salary', v)} + /> + update('workType', v)} + /> + update('status', v)} + /> + + + + + {saving ? '保存中…' : '保存并进入求职端'} + + + 稍后设置 + + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.pageBg }, + body: { padding: 20, paddingBottom: 24 }, + center: { flex: 1, alignItems: 'center', justifyContent: 'center', 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 }, + header: { marginBottom: 20 }, + headerTitle: { fontSize: 22, fontWeight: '800', color: colors.textPrimary }, + headerDesc: { fontSize: 13, color: colors.textSecondary, marginTop: 6 }, + section: { marginBottom: 20 }, + sectionTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginBottom: 10 }, + input: { + height: 50, + borderRadius: 12, + borderWidth: 1, + borderColor: colors.border, + backgroundColor: colors.white, + paddingHorizontal: 14, + fontSize: 16, + color: colors.textPrimary, + }, + chips: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 }, + chip: { + paddingHorizontal: 14, + paddingVertical: 9, + borderRadius: 18, + backgroundColor: colors.white, + borderWidth: 1, + borderColor: colors.border, + }, + chipSelected: { backgroundColor: colors.primaryLight, borderColor: colors.primary }, + chipText: { fontSize: 13, color: colors.textSecondary }, + chipTextSelected: { color: colors.primaryText, fontWeight: '600' }, + footer: { + paddingHorizontal: 20, + paddingVertical: 14, + backgroundColor: colors.white, + borderTopWidth: 1, + borderTopColor: colors.borderLight, + }, + saveBtn: { + height: 50, + borderRadius: 25, + backgroundColor: colors.primaryBtn, + alignItems: 'center', + justifyContent: 'center', + }, + saveBtnDisabled: { backgroundColor: colors.border, opacity: 0.7 }, + saveText: { fontSize: 16, fontWeight: '700', color: colors.textInverse }, + skipBtn: { alignItems: 'center', paddingVertical: 14 }, + skipText: { fontSize: 14, color: colors.textHint }, +});