import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { useRouter } from 'expo-router';
import {
ActivityIndicator,
Alert,
Pressable,
ScrollView,
StyleSheet,
Text,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useOfferCompareData } from '../../src/hooks';
import { buildOfferCompareText } from '../../src/api';
import { shareText } from '../../src/lib/shareFile';
import { EmptyState } from '../../src/components/StateView';
import { colors, headerGradientSolid } from '../../src/theme';
import type { OfferBenefitGroup, OfferItem, OfferScore, WorkLifeBalanceRow } from '../../src/types';
/* ===================== 子组件 ===================== */
/** 顶部导航:返回 + 标题。原先右侧还有「编辑」,但页面从未传入 onEdit,是个点了没反应的入口,已摘掉 */
function Header({ onBack }: { onBack: () => void }) {
return (
Offer对比
);
}
/** 单个 Offer 卡片 */
function OfferCard({ offer, selected }: { offer: OfferItem; selected: boolean }) {
return (
{offer.company}
{selected && }
{offer.position}
{offer.salary}
);
}
/** 星级评分 */
function StarRating({ stars }: { stars: number }) {
return (
{[1, 2, 3, 4, 5].map((i) => (
))}
);
}
/** 综合评分对比 */
function ScoreComparison({ offers, scores }: { offers: OfferItem[]; scores: OfferScore[] }) {
return (
综合评分对比
{offers.map((offer, index) => {
const score = scores[index];
if (!score) return null;
const isWinner = score.score === Math.max(...scores.map((s) => s.score));
return (
{score.score}
{score.level}
推荐指数
);
})}
);
}
/** 分组对比表格 */
function BenefitComparison({ offers, groups }: { offers: OfferItem[]; groups: OfferBenefitGroup[] }) {
return (
薪资福利对比
{/* 表头:公司名 */}
{offers.map((offer) => (
{offer.company}
))}
{groups.map((group) => (
{group.title}
{group.rows.map((row) => (
{row.label}
{row.values.map((value, colIndex) => {
const highlight = row.highlights?.[colIndex];
return (
{value ?? '-'}
);
})}
))}
))}
);
}
/** 工作生活平衡 */
function WorkLifeBalance({ offers, rows }: { offers: OfferItem[]; rows: WorkLifeBalanceRow[] }) {
return (
工作生活平衡
{offers.map((offer) => (
{offer.company}
))}
{rows.map((row) => (
{row.label}
{row.values.map((value, colIndex) => {
const highlight = row.highlights?.[colIndex];
return (
{value}
);
})}
))}
);
}
/**
* AI 智能建议。
* 原先底部挂着「查看完整分析报告」按钮,但建议全文(summary + detail)本卡片已经展示完,
* 点进去没有任何更多内容 —— 属假入口,已摘掉。要留档可用页面底部的「导出对比报告」。
*/
function AISuggestionCard({ summary, detail }: { summary: string; detail: string }) {
return (
AI智能建议
{summary}
{detail}
);
}
/* ===================== 页面 ===================== */
/**
* Offer 对比页:对比已收到 Offer 的薪资、福利、工作生活平衡与 AI 建议
*/
export default function OfferCompareScreen() {
const router = useRouter();
const { data, offers, scores, benefitGroups, workLifeBalance, aiSuggestion, actions, loading, error, refresh } =
useOfferCompareData();
/**
* 导出对比报告:拼成文本经系统分享面板发出(与项目其它导出入口同一套做法)。
* `ok` 只代表面板已拉起,不能据此提示「导出成功」,只在失败时说明原因(见 lib/shareFile)。
*/
const handleExport = async () => {
if (!data) return;
const outcome = await shareText(buildOfferCompareText(data), 'Offer 对比报告');
if (!outcome.ok) Alert.alert('无法导出', outcome.message);
};
if (error) {
return (
router.back()} />
加载失败,请重试
重试
);
}
if (loading || !data) {
return (
router.back()} />
正在加载 Offer 对比…
);
}
return (
router.back()} />
{/* Offer 卡片头部。原先尾部跟着「添加Offer」卡片,但项目没有新增 Offer 的表单页、
后端也没有 POST /api/offers —— 属假入口,已摘掉 */}
{offers.map((offer) => (
))}
{/* 对比结果。offers 为空时下方三个区块会渲染成空壳,故整体给出空态 */}
{offers.length === 0 ? (
) : (
<>
{/* 综合评分对比 */}
{/* 薪资福利对比 */}
{/* 工作生活平衡 */}
{/* AI 智能建议 */}
{aiSuggestion && (
)}
>
)}
{/* 底部操作栏 */}
{actions?.exportLabel ?? '导出对比报告'}
{/* 「接受 Offer」需要 POST /api/offers/:id/accept,须后端支持;保留入口但如实说明,
不假装成功(沿用 4.4 节 AI 按钮的处理方式) */}
// affordance-pending-backend
Alert.alert('接受 Offer', '该操作需要后端支持,当前版本暂未开放')
}
>
{actions?.acceptLabel ?? '接受Offer'}
);
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: colors.pageBg,
},
/* ---- 顶部导航 ---- */
header: {
height: 48,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
backgroundColor: colors.pageBg,
},
headerBtn: {
width: 48,
height: 40,
alignItems: 'center',
justifyContent: 'center',
},
headerTitle: {
flex: 1,
textAlign: 'center',
fontSize: 17,
fontWeight: '700',
color: colors.textPrimary,
},
/* ---- 状态 ---- */
stateWrap: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 24,
},
stateText: {
marginTop: 12,
fontSize: 14,
color: colors.textSecondary,
textAlign: 'center',
},
retryBtn: {
marginTop: 16,
paddingHorizontal: 24,
paddingVertical: 8,
borderRadius: 18,
backgroundColor: colors.primaryBtn,
},
retryText: {
fontSize: 13,
fontWeight: '600',
color: colors.white,
},
/* ---- 页面内容 ---- */
scroll: {
flex: 1,
},
content: {
paddingHorizontal: 12,
paddingTop: 8,
paddingBottom: 12,
},
bottomGap: {
height: 80,
},
/* ---- Offer 卡片 ---- */
offerList: {
gap: 10,
paddingVertical: 4,
paddingHorizontal: 4,
},
offerCard: {
width: 150,
padding: 14,
borderRadius: 8,
backgroundColor: colors.white,
borderWidth: 1,
borderColor: colors.border,
},
offerCardSelected: {
borderColor: colors.primary,
backgroundColor: colors.primaryLight,
},
offerCardTop: {
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'space-between',
},
offerCompany: {
flex: 1,
fontSize: 14,
fontWeight: '700',
color: colors.textPrimary,
},
checkCircle: {
width: 18,
height: 18,
borderRadius: 9,
borderWidth: 1.5,
borderColor: colors.borderStrong,
alignItems: 'center',
justifyContent: 'center',
marginLeft: 6,
},
checkCircleActive: {
backgroundColor: colors.primary,
borderColor: colors.primary,
},
offerPosition: {
marginTop: 6,
fontSize: 12,
color: colors.textSecondary,
},
offerSalary: {
marginTop: 10,
fontSize: 16,
fontWeight: '800',
},
/* ---- 通用卡片与分区标题 ---- */
card: {
backgroundColor: colors.white,
borderRadius: 8,
padding: 16,
marginTop: 12,
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 14,
},
sectionBar: {
width: 4,
height: 16,
borderRadius: 2,
backgroundColor: colors.primary,
marginRight: 8,
},
sectionTitle: {
fontSize: 16,
fontWeight: '700',
color: colors.textPrimary,
},
/* ---- 综合评分对比 ---- */
scoreRow: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingTop: 4,
},
scoreCol: {
alignItems: 'center',
flex: 1,
},
scoreNum: {
fontSize: 44,
fontWeight: '800',
color: colors.textSecondary,
},
scoreNumWinner: {
color: colors.success,
},
scoreMeta: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 6,
gap: 6,
},
scoreLevel: {
fontSize: 13,
fontWeight: '700',
},
starRow: {
flexDirection: 'row',
},
starIcon: {
marginHorizontal: 1,
},
scoreHint: {
marginTop: 6,
fontSize: 11,
color: colors.textHint,
},
/* ---- 表格 ---- */
tableHeaderRow: {
flexDirection: 'row',
alignItems: 'center',
paddingBottom: 10,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
tableRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
tableLabelCell: {
width: 88,
},
tableLabelText: {
width: 88,
fontSize: 13,
color: colors.textSecondary,
},
tableValueCell: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 8,
marginHorizontal: 2,
borderRadius: 8,
},
tableHeaderText: {
fontSize: 13,
fontWeight: '600',
color: colors.textPrimary,
textAlign: 'center',
},
tableValueText: {
fontSize: 13,
color: colors.textPrimary,
textAlign: 'center',
},
tableValueTextHighlight: {
color: colors.primaryText,
fontWeight: '700',
},
tableValueTextSuccess: {
color: colors.successText,
fontWeight: '700',
},
offerDot: {
width: 6,
height: 6,
borderRadius: 3,
marginRight: 4,
},
groupTitle: {
marginTop: 14,
marginBottom: 8,
fontSize: 14,
fontWeight: '700',
color: colors.textPrimary,
},
/* ---- AI 建议 ---- */
aiCard: {
marginTop: 12,
borderRadius: 8,
padding: 16,
},
aiHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
},
aiTitle: {
marginLeft: 6,
fontSize: 16,
fontWeight: '700',
color: colors.textInverse,
},
aiSummary: {
fontSize: 14,
fontWeight: '700',
color: colors.textInverse,
lineHeight: 20,
},
aiDetail: {
marginTop: 8,
fontSize: 13,
color: colors.textInverse,
opacity: 0.9,
lineHeight: 19,
},
/* ---- 底部操作栏 ---- */
actionBar: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 12,
paddingTop: 10,
paddingBottom: 16,
backgroundColor: colors.white,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
gap: 10,
},
exportBtn: {
flex: 1,
height: 44,
borderRadius: 22,
borderWidth: 1,
borderColor: colors.border,
alignItems: 'center',
justifyContent: 'center',
},
exportText: {
fontSize: 14,
fontWeight: '600',
color: colors.textPrimary,
},
acceptBtn: {
flex: 1.4,
height: 44,
borderRadius: 22,
backgroundColor: colors.primaryBtn,
alignItems: 'center',
justifyContent: 'center',
},
acceptText: {
fontSize: 14,
fontWeight: '700',
color: colors.white,
},
});