You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
353 lines
12 KiB
353 lines
12 KiB
import { Ionicons } from '@expo/vector-icons';
|
|
import { useRouter } from 'expo-router';
|
|
import { useState } from 'react';
|
|
import {
|
|
ActivityIndicator,
|
|
Pressable,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
View,
|
|
} 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';
|
|
|
|
/* ===================== 子组件:顶部导航 ===================== */
|
|
|
|
function Header({ onBack }: { onBack: () => void }) {
|
|
return (
|
|
<View style={styles.header}>
|
|
<Pressable style={styles.headerBtn} onPress={onBack} hitSlop={8}>
|
|
<Ionicons name="chevron-back" size={24} color={colors.textPrimary} />
|
|
</Pressable>
|
|
<Text style={styles.headerTitle}>企业认证</Text>
|
|
<View style={styles.headerBtn} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:状态徽章 ===================== */
|
|
|
|
function StatusBadge({ status }: { status: EnterpriseCertificationStatus }) {
|
|
const map: Record<EnterpriseCertificationStatus, { text: string; bg: string; color: string }> = {
|
|
unverified: { text: '未认证', bg: colors.borderLight, color: colors.textSecondary },
|
|
pending: { text: '审核中', bg: colors.primaryLight, color: colors.primaryText },
|
|
verified: { text: '已认证', bg: colors.successLight, color: colors.successText },
|
|
rejected: { text: '未通过', bg: colors.dangerLight, color: colors.dangerText },
|
|
};
|
|
const theme = map[status];
|
|
return (
|
|
<View style={[styles.statusBadge, { backgroundColor: theme.bg }]}>
|
|
<Text style={[styles.statusText, { color: theme.color }]}>{theme.text}</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 子组件:输入框 ===================== */
|
|
|
|
function Field({
|
|
label,
|
|
required,
|
|
value,
|
|
placeholder,
|
|
onChange,
|
|
keyboardType = 'default',
|
|
maxLength,
|
|
}: {
|
|
label: string;
|
|
required?: boolean;
|
|
value: string;
|
|
placeholder: string;
|
|
onChange: (v: string) => void;
|
|
keyboardType?: 'default' | 'number-pad' | 'phone-pad';
|
|
maxLength?: number;
|
|
}) {
|
|
return (
|
|
<View style={styles.field}>
|
|
<Text style={styles.fieldLabel}>
|
|
{label}
|
|
{required ? <Text style={styles.required}> *</Text> : null}
|
|
</Text>
|
|
<TextInput
|
|
style={styles.fieldInput}
|
|
value={value}
|
|
placeholder={placeholder}
|
|
placeholderTextColor={colors.textHint}
|
|
onChangeText={onChange}
|
|
keyboardType={keyboardType}
|
|
maxLength={maxLength}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/* ===================== 页面 ===================== */
|
|
|
|
export default function EnterpriseCertificationScreen() {
|
|
const router = useRouter();
|
|
const { completeEnterpriseVerify } = useAuth();
|
|
const { certification, loading, error, refresh, submitting, submit, status } = useEnterpriseCertification();
|
|
const [form, setForm] = useState<EnterpriseCertificationForm>({
|
|
companyName: '',
|
|
creditCode: '',
|
|
contactName: '',
|
|
contactPhone: '',
|
|
});
|
|
|
|
if (loading || !certification) {
|
|
return (
|
|
<SafeAreaView style={styles.safeArea} edges={['top']}>
|
|
<Header onBack={() => router.back()} />
|
|
<View style={styles.center}>
|
|
<ActivityIndicator size="large" color={colors.primary} />
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<SafeAreaView style={styles.safeArea} edges={['top']}>
|
|
<Header onBack={() => router.back()} />
|
|
<View style={styles.center}>
|
|
<Text style={styles.tip}>加载失败</Text>
|
|
<Pressable style={styles.retryBtn} onPress={refresh}>
|
|
<Text style={styles.retryText}>重试</Text>
|
|
</Pressable>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const currentStatus = status ?? certification.status;
|
|
const isVerified = currentStatus === 'verified';
|
|
const isPending = currentStatus === 'pending';
|
|
|
|
const updateField = <K extends keyof EnterpriseCertificationForm>(field: K, value: EnterpriseCertificationForm[K]) => {
|
|
setForm((prev) => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (!form.companyName || !form.creditCode || !form.contactName || !form.contactPhone) return;
|
|
await submit(form);
|
|
// 提交即视为走完「企业认证」必经步骤,标记后进入企业端(审核中也可浏览)
|
|
completeEnterpriseVerify();
|
|
router.replace('/(enterprise)/job-management');
|
|
};
|
|
|
|
const handleSkip = () => {
|
|
// 允许稍后认证:标记步骤已完成,先进企业端
|
|
completeEnterpriseVerify();
|
|
router.replace('/(enterprise)/job-management');
|
|
};
|
|
|
|
return (
|
|
<SafeAreaView style={styles.safeArea} edges={['top']}>
|
|
<Header onBack={() => router.back()} />
|
|
<ScrollView style={styles.scroll} showsVerticalScrollIndicator={false}>
|
|
{/* 顶部认证说明 */}
|
|
<View style={styles.topCard}>
|
|
<View style={styles.topHeader}>
|
|
<View style={styles.shieldWrap}>
|
|
<Ionicons name="shield-checkmark" size={28} color={colors.textInverse} />
|
|
</View>
|
|
<View style={styles.topTitleWrap}>
|
|
<Text style={styles.topTitle}>企业认证</Text>
|
|
<StatusBadge status={currentStatus} />
|
|
</View>
|
|
</View>
|
|
<Text style={styles.topDesc}>认证后可享受</Text>
|
|
<View style={styles.benefits}>
|
|
{certification.benefits.map((b) => (
|
|
<View key={b.id} style={styles.benefitRow}>
|
|
<Ionicons name="checkmark-circle" size={16} color={colors.success} />
|
|
<Text style={styles.benefitText}>{b.text}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{/* 表单 */}
|
|
{isVerified ? (
|
|
<View style={styles.successCard}>
|
|
<Ionicons name="checkmark-circle-outline" size={48} color={colors.success} />
|
|
<Text style={styles.successTitle}>企业认证已通过</Text>
|
|
<Text style={styles.successDesc}>您已进入企业端,可发布岗位并管理招聘流程</Text>
|
|
<Pressable style={styles.successBtn} onPress={() => router.replace('/(enterprise)/job-management')}>
|
|
<Text style={styles.successBtnText}>进入企业端</Text>
|
|
</Pressable>
|
|
</View>
|
|
) : isPending ? (
|
|
<View style={styles.pendingCard}>
|
|
<Ionicons name="time-outline" size={48} color={colors.primary} />
|
|
<Text style={styles.pendingTitle}>认证审核中</Text>
|
|
<Text style={styles.pendingDesc}>我们将在 1-3 个工作日内完成审核,请耐心等待</Text>
|
|
</View>
|
|
) : (
|
|
<View style={styles.formCard}>
|
|
<Text style={styles.formTitle}>填写企业信息</Text>
|
|
<Field
|
|
label="企业名称"
|
|
required
|
|
value={form.companyName}
|
|
placeholder="请输入企业全称"
|
|
onChange={(v) => updateField('companyName', v)}
|
|
/>
|
|
<Field
|
|
label="统一社会信用代码"
|
|
required
|
|
value={form.creditCode}
|
|
placeholder="请输入18位统一社会信用代码"
|
|
onChange={(v) => updateField('creditCode', v)}
|
|
maxLength={18}
|
|
/>
|
|
<Field
|
|
label="联系人姓名"
|
|
required
|
|
value={form.contactName}
|
|
placeholder="请输入联系人姓名"
|
|
onChange={(v) => updateField('contactName', v)}
|
|
/>
|
|
<Field
|
|
label="联系人手机号"
|
|
required
|
|
value={form.contactPhone}
|
|
placeholder="请输入联系人手机号"
|
|
onChange={(v) => updateField('contactPhone', v)}
|
|
keyboardType="phone-pad"
|
|
maxLength={11}
|
|
/>
|
|
<Pressable
|
|
style={[styles.submitBtn, submitting && styles.submitBtnDisabled]}
|
|
onPress={handleSubmit}
|
|
disabled={submitting}
|
|
>
|
|
<Text style={styles.submitBtnText}>{submitting ? '提交中…' : '提交认证'}</Text>
|
|
</Pressable>
|
|
<Pressable
|
|
style={{ alignItems: 'center', paddingVertical: 14 }}
|
|
onPress={handleSkip}
|
|
hitSlop={6}
|
|
>
|
|
<Text style={{ fontSize: 14, color: colors.textHint }}>稍后认证</Text>
|
|
</Pressable>
|
|
</View>
|
|
)}
|
|
<View style={styles.bottomPad} />
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
safeArea: { flex: 1, backgroundColor: colors.pageBg },
|
|
header: {
|
|
height: 44,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: 8,
|
|
backgroundColor: colors.white,
|
|
},
|
|
headerBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },
|
|
headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary },
|
|
scroll: { flex: 1 },
|
|
center: { flex: 1, alignItems: 'center', justifyContent: 'center' },
|
|
tip: { fontSize: 14, color: colors.textSecondary },
|
|
retryBtn: {
|
|
marginTop: 12,
|
|
paddingHorizontal: 18,
|
|
paddingVertical: 8,
|
|
borderRadius: 16,
|
|
backgroundColor: colors.primaryLight,
|
|
},
|
|
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
|
|
topCard: {
|
|
margin: 12,
|
|
backgroundColor: colors.primary,
|
|
borderRadius: 16,
|
|
padding: 20,
|
|
},
|
|
topHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 16 },
|
|
shieldWrap: {
|
|
width: 48,
|
|
height: 48,
|
|
borderRadius: 24,
|
|
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
topTitleWrap: { flex: 1, marginLeft: 14 },
|
|
topTitle: { fontSize: 20, fontWeight: '800', color: colors.textInverse },
|
|
topDesc: { fontSize: 14, color: colors.textInverse, opacity: 0.85, marginBottom: 12 },
|
|
benefits: { gap: 8 },
|
|
benefitRow: { flexDirection: 'row', alignItems: 'center' },
|
|
benefitText: { marginLeft: 8, fontSize: 13, color: colors.textInverse, opacity: 0.9 },
|
|
statusBadge: {
|
|
alignSelf: 'flex-start',
|
|
marginTop: 6,
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 2,
|
|
borderRadius: 10,
|
|
},
|
|
statusText: { fontSize: 11, fontWeight: '600' },
|
|
formCard: {
|
|
marginHorizontal: 12,
|
|
backgroundColor: colors.white,
|
|
borderRadius: 16,
|
|
padding: 20,
|
|
},
|
|
formTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary, marginBottom: 16 },
|
|
field: { marginBottom: 16 },
|
|
fieldLabel: { fontSize: 14, fontWeight: '500', color: colors.textPrimary, marginBottom: 8 },
|
|
required: { color: colors.danger },
|
|
fieldInput: {
|
|
height: 46,
|
|
borderWidth: 1,
|
|
borderColor: colors.border,
|
|
borderRadius: 10,
|
|
paddingHorizontal: 14,
|
|
fontSize: 14,
|
|
color: colors.textPrimary,
|
|
},
|
|
submitBtn: {
|
|
marginTop: 8,
|
|
backgroundColor: colors.primaryBtn,
|
|
borderRadius: 12,
|
|
paddingVertical: 14,
|
|
alignItems: 'center',
|
|
},
|
|
submitBtnDisabled: { opacity: 0.6 },
|
|
submitBtnText: { fontSize: 16, fontWeight: '700', color: colors.textInverse },
|
|
successCard: {
|
|
marginHorizontal: 12,
|
|
backgroundColor: colors.white,
|
|
borderRadius: 16,
|
|
padding: 32,
|
|
alignItems: 'center',
|
|
},
|
|
successTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginTop: 16 },
|
|
successDesc: { fontSize: 13, color: colors.textSecondary, marginTop: 8, textAlign: 'center' },
|
|
successBtn: {
|
|
marginTop: 24,
|
|
backgroundColor: colors.successBtn,
|
|
borderRadius: 12,
|
|
paddingHorizontal: 32,
|
|
paddingVertical: 12,
|
|
},
|
|
successBtnText: { fontSize: 15, fontWeight: '700', color: colors.textInverse },
|
|
pendingCard: {
|
|
marginHorizontal: 12,
|
|
backgroundColor: colors.white,
|
|
borderRadius: 16,
|
|
padding: 32,
|
|
alignItems: 'center',
|
|
},
|
|
pendingTitle: { fontSize: 18, fontWeight: '700', color: colors.textPrimary, marginTop: 16 },
|
|
pendingDesc: { fontSize: 13, color: colors.textSecondary, marginTop: 8, textAlign: 'center' },
|
|
bottomPad: { height: 24 },
|
|
});
|
|
|