import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router';
import {
ActivityIndicator,
Pressable,
ScrollView,
StyleSheet,
Switch,
Text,
TextInput,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useJobPublish } from '../src/hooks';
import { colors } from '../src/theme';
import type { JobPublishFormData } from '../src/types';
const WORK_TYPE_OPTIONS: { label: string; value: JobPublishFormData['workType'] }[] = [
{ label: '全职', value: 'full-time' },
{ label: '兼职', value: 'part-time' },
{ label: '实习', value: 'intern' },
{ label: '远程', value: 'remote' },
];
const EDUCATION_OPTIONS: JobPublishFormData['education'][] = ['不限', '大专', '本科', '硕士', '博士'];
const EXPERIENCE_OPTIONS: JobPublishFormData['experience'][] = [
'应届生',
'1年以内',
'1-3年',
'3-5年',
'5-10年',
'10年以上',
];
const BENEFIT_OPTIONS = ['五险一金', '年终奖金', '股票期权', '带薪年假', '弹性工作', '餐补', '交通补助', '定期体检'];
const SKILL_OPTIONS = ['React', 'Vue', 'TypeScript', 'Node.js', 'Webpack', 'Git', 'Python', 'Java', 'Go', 'SQL'];
/* ===================== 子组件:顶部导航 ===================== */
function Header({ onBack, onSave }: { onBack: () => void; onSave: () => void }) {
return (
发布岗位
保存
);
}
/* ===================== 子组件:表单项 ===================== */
function Field({ label, required, children }: { label: string; required?: boolean; children: React.ReactNode }) {
return (
{label}
{required && *}
{children}
);
}
function Input({ value, onChangeText, placeholder, multiline }: {
value: string;
onChangeText: (text: string) => void;
placeholder?: string;
multiline?: boolean;
}) {
return (
);
}
function NumberInput({ value, onChangeText, placeholder }: {
value: number;
onChangeText: (text: string) => void;
placeholder?: string;
}) {
return (
);
}
/* ===================== 子组件:单选标签组 ===================== */
function RadioGroup({
options,
value,
onChange,
}: {
options: T[] | { label: string; value: T }[];
value: T;
onChange: (value: T) => void;
}) {
return (
{options.map((opt) => {
const optionValue = typeof opt === 'string' ? opt : opt.value;
const optionLabel = typeof opt === 'string' ? opt : opt.label;
const selected = optionValue === value;
return (
onChange(optionValue)}
>
{optionLabel}
);
})}
);
}
/* ===================== 子组件:多选标签组 ===================== */
function CheckboxGroup({ options, values, onToggle }: { options: string[]; values: string[]; onToggle: (v: string) => void }) {
return (
{options.map((opt) => {
const selected = values.includes(opt);
return (
onToggle(opt)}
>
{selected && }
{opt}
);
})}
);
}
/* ===================== 子组件:开关行 ===================== */
function SwitchRow({ label, value, onValueChange }: { label: string; value: boolean; onValueChange: (v: boolean) => void }) {
return (
{label}
);
}
/* ===================== 子组件:质量预估卡 ===================== */
function QualityEstimateCard({ score, level, suggestions, estimating }: {
score: number;
level: string;
suggestions: string[];
estimating: boolean;
}) {
return (
岗位质量预估
AI 实时评估岗位信息完整度
{estimating ? (
) : (
<>
{score}
{level}
>
)}
{suggestions.length > 0 && (
{suggestions.map((s, i) => (
{s}
))}
)}
);
}
/* ===================== 页面主组件 ===================== */
export default function JobPublishScreen() {
const router = useRouter();
const {
form,
loading,
error,
updateStringField,
updateNumberField,
updateField,
toggleBenefit,
toggleSkill,
estimate,
estimating,
runEstimate,
publishing,
publish,
} = useJobPublish();
if (loading) {
return (
);
}
if (error || !form) {
return (
加载失败,请稍后重试
);
}
return (
router.back()} onSave={() => {}} />
{/* 基本信息 */}
基本信息
updateStringField('positionName', v)} placeholder="请输入岗位名称" />
updateStringField('department', v)} placeholder="请输入所属部门" />
updateNumberField('headcount', v)} placeholder="请输入招聘人数" />
updateStringField('city', v)} placeholder="请输入工作城市" />
updateStringField('address', v)} placeholder="请输入详细地址" />
options={WORK_TYPE_OPTIONS}
value={form.workType}
onChange={(v) => updateField('workType', v)}
/>
{/* 岗位要求 */}
岗位要求
options={EDUCATION_OPTIONS}
value={form.education}
onChange={(v) => updateField('education', v)}
/>
options={EXPERIENCE_OPTIONS}
value={form.experience}
onChange={(v) => updateField('experience', v)}
/>
{/* 薪资福利 */}
薪资福利
updateNumberField('salaryMin', v)} placeholder="最低" />
-
updateNumberField('salaryMax', v)} placeholder="最高" />
updateNumberField('salaryMonths', v)} placeholder="如 14" />
{/* 岗位描述 */}
岗位描述
updateStringField('responsibilities', v)} placeholder="请输入岗位职责" multiline />
updateStringField('requirements', v)} placeholder="请输入岗位要求" multiline />
{/* 岗位设置 */}
岗位设置
options={[
{ label: '全部可见', value: 'all' as const },
{ label: '内部推荐', value: 'internal' as const },
{ label: '校园招聘', value: 'campus' as const },
]}
value={form.visibility}
onChange={(v) => updateField('visibility', v)}
/>
updateField('urgent', v)} />
updateField('acceptFresh', v)} />
updateStringField('deadline', v)} placeholder="YYYY-MM-DD" />
{/* 质量预估 */}
质量预估
{publishing ? '发布中...' : '发布岗位'}
);
}
/* ===================== 样式 ===================== */
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.pageBg,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.pageBg,
},
errorText: {
color: colors.textSecondary,
fontSize: 14,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: colors.white,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
headerBtn: {
width: 36,
height: 36,
justifyContent: 'center',
alignItems: 'center',
},
headerTitle: {
fontSize: 17,
fontWeight: '600',
color: colors.textPrimary,
},
headerSaveBtn: {
paddingHorizontal: 12,
paddingVertical: 6,
backgroundColor: colors.primaryLight,
borderRadius: 16,
},
headerSaveText: {
fontSize: 13,
fontWeight: '600',
color: colors.primary,
},
scroll: {
flex: 1,
},
scrollContent: {
padding: 16,
paddingBottom: 100,
},
card: {
backgroundColor: colors.white,
borderRadius: 16,
padding: 16,
marginBottom: 16,
shadowColor: colors.shadow,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.04,
shadowRadius: 6,
elevation: 2,
},
cardTitle: {
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,
},
input: {
height: 44,
borderWidth: 1,
borderColor: colors.border,
borderRadius: 10,
paddingHorizontal: 12,
fontSize: 14,
color: colors.textPrimary,
backgroundColor: colors.pageBg,
},
textarea: {
height: 100,
paddingTop: 12,
paddingBottom: 12,
},
tagRow: {
flexDirection: 'row',
flexWrap: 'wrap',
marginHorizontal: -4,
},
radioTag: {
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 18,
borderWidth: 1,
borderColor: colors.border,
margin: 4,
backgroundColor: colors.white,
},
radioTagActive: {
backgroundColor: colors.primary,
borderColor: colors.primary,
},
radioText: {
fontSize: 13,
color: colors.textPrimary,
},
radioTextActive: {
color: colors.white,
fontWeight: '500',
},
checkboxTag: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 12,
paddingVertical: 7,
borderRadius: 18,
borderWidth: 1,
borderColor: colors.border,
margin: 4,
backgroundColor: colors.white,
},
checkboxTagActive: {
backgroundColor: colors.primary,
borderColor: colors.primary,
},
checkboxText: {
fontSize: 13,
color: colors.textPrimary,
marginLeft: 4,
},
checkboxTextActive: {
color: colors.white,
fontWeight: '500',
},
switchRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
switchLabel: {
fontSize: 14,
color: colors.textPrimary,
},
salaryRow: {
flexDirection: 'row',
alignItems: 'flex-end',
},
salarySeparator: {
marginHorizontal: 8,
marginBottom: 16,
fontSize: 16,
color: colors.textSecondary,
},
qualityCard: {
backgroundColor: colors.primaryLight,
borderWidth: 1,
borderColor: colors.primaryBorder,
},
qualityHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
qualitySub: {
fontSize: 12,
color: colors.textSecondary,
marginTop: 4,
},
qualityScoreBox: {
alignItems: 'center',
},
qualityScore: {
fontSize: 32,
fontWeight: '700',
color: colors.primary,
},
qualityLevel: {
fontSize: 13,
color: colors.primaryText,
fontWeight: '500',
},
suggestionList: {
marginTop: 16,
paddingTop: 16,
borderTopWidth: 1,
borderTopColor: colors.primaryBorder,
},
suggestionItem: {
flexDirection: 'row',
alignItems: 'flex-start',
marginBottom: 8,
},
suggestionText: {
flex: 1,
fontSize: 13,
color: colors.textSecondary,
marginLeft: 8,
lineHeight: 18,
},
footer: {
flexDirection: 'row',
padding: 16,
paddingBottom: 24,
backgroundColor: colors.white,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
},
draftBtn: {
flex: 1,
height: 46,
borderRadius: 24,
borderWidth: 1,
borderColor: colors.primary,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
draftText: {
fontSize: 15,
fontWeight: '600',
color: colors.primary,
},
publishBtn: {
flex: 2,
height: 46,
borderRadius: 24,
backgroundColor: colors.primaryBtn,
justifyContent: 'center',
alignItems: 'center',
},
publishBtnDisabled: {
opacity: 0.6,
},
publishText: {
fontSize: 15,
fontWeight: '600',
color: colors.white,
},
});