轻松聘
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.
 
 
 

225 lines
7.3 KiB

import { useLocalSearchParams, useRouter } from 'expo-router';
import { useEffect, useState } from 'react';
import { Alert, 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,
Field,
FormNote,
IconAction,
PageFooter,
} from '../../components/resume/EditKit';
import { useResume } from '../../src/hooks';
import { colors } from '../../src/theme';
import type { ResumeProject } from '../../src/types';
/**
* 项目经历编辑页:一页两态(列表态 / 表单态)。
*
* 本页不提供「AI 优化」按钮:项目经历在首页只有「已量化」徽标,
* 徽标的置位要等后端真能改写正文,不能让用户点出一个立刻显示「已优化」的假状态。
*/
export default function ResumeProjectScreen() {
const router = useRouter();
const params = useLocalSearchParams<{ new?: string; id?: string }>();
const { resume, loading, error, refresh, saving, saveProjects } = useResume();
const [editing, setEditing] = useState<ProjForm | null>(null);
const [entryApplied, setEntryApplied] = useState(false);
useEffect(() => {
if (!resume || entryApplied) return;
setEntryApplied(true);
if (params.new === '1') {
setEditing(EMPTY_FORM);
return;
}
if (params.id !== undefined && params.id !== '') {
const found = resume.projects.find((it) => it.id === params.id);
if (found) setEditing(toForm(found));
}
}, [resume, entryApplied, params.new, params.id]);
const list = resume?.projects ?? [];
const header = (
<EditHeader
title={editing === null ? '项目经历' : editing.id === undefined ? '添加项目经历' : '编辑项目经历'}
onBack={() => (editing === null ? router.back() : setEditing(null))}
/>
);
if (resume === null) {
return (
<SafeAreaView style={styles.container} edges={['top']}>
{header}
{error !== null ? (
<CenterState icon="alert-circle-outline" text="加载失败" actionLabel="点击重试" onAction={refresh} />
) : (
<CenterState text={loading ? '正在加载…' : '简历数据为空'} />
)}
</SafeAreaView>
);
}
/* ===================== 表单态 ===================== */
if (editing !== null) {
const name = editing.name.trim();
const canSave = name !== '';
const update = (patch: Partial<ProjForm>) => setEditing((prev) => ({ ...(prev ?? editing), ...patch }));
const handleSubmit = async () => {
if (!canSave || saving) return;
const fields = {
name,
role: editing.role.trim(),
description: editing.description.trim(),
aiOptimized: false,
};
const next =
editing.id === undefined
? [...list, fields]
: list.map((it) => (it.id === editing.id ? { ...fields, id: editing.id } : it));
await saveProjects(next);
setEditing(null);
};
return (
<SafeAreaView style={styles.container} edges={['top']}>
{header}
<ScrollView
contentContainerStyle={styles.body}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
<Field
label="项目名称"
required
value={editing.name}
onChangeText={(v) => update({ name: v })}
placeholder="请输入项目名称"
maxLength={40}
autoFocus
/>
<Field
label="担任角色"
value={editing.role}
onChangeText={(v) => update({ role: v })}
placeholder="如:前端负责人"
maxLength={20}
/>
<Field
label="项目描述"
value={editing.description}
onChangeText={(v) => update({ description: v })}
placeholder="做了什么、结果如何(有数字更好,如:首屏从 3.5s 降至 1.2s)"
multiline
maxLength={500}
/>
<FormNote text="项目名称为必填;角色与描述没填就不在简历里显示空行。" />
</ScrollView>
<PageFooter>
<PrimaryButton label={saving ? '保存中…' : '保存'} active={canSave} onPress={handleSubmit} loading={saving} />
</PageFooter>
</SafeAreaView>
);
}
/* ===================== 列表态 ===================== */
const handleDelete = (item: ResumeProject) => {
Alert.alert('删除这条项目经历?', item.name, [
{ text: '取消', style: 'cancel' },
{
text: '删除',
style: 'destructive',
onPress: () => {
void saveProjects(list.filter((it) => it.id !== item.id));
},
},
]);
};
return (
<SafeAreaView style={styles.container} edges={['top']}>
{header}
{list.length === 0 ? (
<CenterState icon="albums-outline" text="还没有项目经历" />
) : (
<ScrollView contentContainerStyle={styles.body} showsVerticalScrollIndicator={false}>
{list.map((proj) => (
<View key={proj.id} style={styles.row}>
<View style={styles.rowBody}>
<Text style={styles.rowTitle}>{proj.name}</Text>
{proj.role !== '' && <Text style={styles.rowSub}>{proj.role}</Text>}
{proj.description !== '' && (
<Text style={styles.rowDesc} numberOfLines={2}>
{proj.description}
</Text>
)}
</View>
<IconAction
icon="create-outline"
label={`编辑 ${proj.name}`}
color={colors.primary}
onPress={() => setEditing(toForm(proj))}
/>
<IconAction
icon="trash-outline"
label={`删除 ${proj.name}`}
color={colors.danger}
onPress={() => handleDelete(proj)}
/>
</View>
))}
</ScrollView>
)}
<PageFooter>
<PrimaryButton label="添加项目经历" onPress={() => setEditing(EMPTY_FORM)} />
</PageFooter>
</SafeAreaView>
);
}
/* ===================== 表单与条目互转 ===================== */
interface ProjForm {
/** undefined = 新增项(由 API 层补 id) */
id?: string;
name: string;
role: string;
description: string;
}
const EMPTY_FORM: ProjForm = { name: '', role: '', description: '' };
const toForm = (proj: ResumeProject): ProjForm => ({
id: proj.id,
name: proj.name,
role: proj.role,
description: proj.description,
});
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.pageBg },
body: { padding: 20, paddingBottom: 24 },
row: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.white,
borderRadius: 14,
paddingVertical: 14,
paddingLeft: 16,
paddingRight: 8,
marginBottom: 10,
borderWidth: 1,
borderColor: colors.borderLight,
},
rowBody: { flex: 1, gap: 4 },
rowTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary },
rowSub: { fontSize: 13, color: colors.textSecondary },
rowDesc: { fontSize: 13, color: colors.textHint, lineHeight: 18 },
});