import { Ionicons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { useRouter } from 'expo-router'; import { useState } from 'react'; import { Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useSalaryCalculator } from '../../src/hooks'; import { colors, headerGradientShort } from '../../src/theme'; import type { SalaryCalcInput, SalaryToolLink } from '../../src/types'; import { formatCurrency } from '../../src/utils'; /* ===================== 子组件:顶部导航 ===================== */ function Header({ onBack, onHistory }: { onBack: () => void; onHistory: () => void }) { return ( 薪资计算器 历史记录 ); } /* ===================== 子组件:数字输入行 ===================== */ function NumberInput({ label, value, suffix, onChange, placeholder, }: { label: string; value: number; suffix: string; onChange: (v: number) => void; placeholder?: string; }) { const [text, setText] = useState(String(value)); return ( {label} { setText(t); const n = parseFloat(t); if (!Number.isNaN(n)) onChange(n); }} keyboardType="numeric" placeholder={placeholder} placeholderTextColor={colors.textHint} /> {suffix} ); } function SelectRow({ label, value, onPress, }: { label: string; value: string; onPress: () => void; }) { return ( {label} {value} ); } /* ===================== 子组件:五险一金明细 ===================== */ function InsuranceCard({ result }: { result: import('../../src/types').SalaryCalcResult }) { return ( 五险一金明细 个人缴纳 缴纳比例 {result.insuranceItems.map((item) => ( {item.label} {formatCurrency(item.amount)} {(item.rate * 100).toFixed(1)}% ))} 合计个人缴纳 {formatCurrency(result.monthlySocial)} ); } /* ===================== 子组件:个人所得税明细 ===================== */ function TaxCard({ result }: { result: import('../../src/types').SalaryCalcResult }) { const taxItems = result.taxItems; const monthlyTax = taxItems.find((t) => t.label === '月度个税')?.amount ?? 0; const annualTax = taxItems.find((t) => t.label === '年度个税')?.amount ?? 0; const taxable = taxItems.find((t) => t.label === '应纳税所得额')?.amount ?? 0; const rate = taxItems.find((t) => t.label === '适用税率')?.amount ?? 0; return ( 个人所得税明细 应纳税所得额 {formatCurrency(taxable)} 适用税率 {(rate * 100).toFixed(0)}% 月度个税 {formatCurrency(monthlyTax)}/月 年度个税 {formatCurrency(annualTax)}/年 ); } /* ===================== 子组件:薪资结构 ===================== */ function StructureCard({ result }: { result: import('../../src/types').SalaryCalcResult }) { const items = result.structure.filter((s) => s.label !== '税前工资'); const themeColors: Record = { primary: colors.primary, info: colors.info, action: colors.action, success: colors.success, }; return ( 薪资结构 {items.map((item, index) => { const start = items.slice(0, index).reduce((sum, i) => sum + i.percent, 0); return ( ); })} {items.map((item) => ( {item.label} {formatCurrency(item.value / 1000, 0)}K {item.percent}% ))} ); } /* ===================== 子组件:更多工具 ===================== */ function ToolLink({ tool }: { tool: SalaryToolLink }) { return ( {tool.title} {tool.desc} ); } /* ===================== 页面 ===================== */ export default function SalaryCalculatorScreen() { const router = useRouter(); const { inputs, setInputs, result } = useSalaryCalculator(); const update = (key: K, value: SalaryCalcInput[K]) => { setInputs({ ...inputs, [key]: value }); }; return (
router.back()} onHistory={() => {}} /> {/* 输入卡片 */} 收入参数 update('monthlyGross', Math.max(0, v))} /> {}} /> update('socialBase', Math.max(0, v))} /> update('providentFundRate', Math.max(0, Math.min(100, v)) / 100)} /> update('specialDeduction', Math.max(0, v))} /> {/* 结果卡片 */} 税后月工资 {formatCurrency(result.monthlyNet)} /月 {/* 五险一金 */} {/* 个税 */} {/* 薪资结构 */} {/* 更多工具 */} 更多工具 {[ { id: 'year-end', title: '年终奖个税计算', desc: '单独计税 / 并入综合所得对比', icon: 'gift-outline', route: '/tools/salary-calculator', }, { id: 'severance', title: '离职补偿金计算', desc: 'N+1 补偿金与个税估算', icon: 'cash-outline', route: '/tools/salary-calculator', }, { id: 'proof', title: '公积金/社保证明', desc: '一键查询缴纳记录', icon: 'document-text-outline', route: '/tools/salary-calculator', }, ].map((tool) => ( ))} {}}> 保存计算记录 ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.pageBg }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 12, paddingVertical: 12, backgroundColor: colors.white, }, headerBtn: { width: 60, justifyContent: 'center' }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary }, headerAction: { fontSize: 14, color: colors.primary, textAlign: 'right' }, body: { padding: 16, paddingBottom: 32 }, card: { backgroundColor: colors.white, borderRadius: 14, padding: 16, marginBottom: 12, borderWidth: 1, borderColor: colors.borderLight, }, cardTitle: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, marginBottom: 14 }, inputRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 12, borderTopWidth: 1, borderTopColor: colors.borderLight, }, inputLabel: { fontSize: 14, color: colors.textPrimary }, inputBox: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.pageBg, borderRadius: 10, paddingHorizontal: 12, paddingVertical: 8, }, input: { minWidth: 80, fontSize: 15, fontWeight: '700', color: colors.textPrimary, textAlign: 'right', }, inputSuffix: { fontSize: 13, color: colors.textSecondary, marginLeft: 4 }, selectBox: { flexDirection: 'row', alignItems: 'center' }, selectValue: { fontSize: 15, fontWeight: '600', color: colors.textPrimary, marginRight: 4 }, resultCard: { borderRadius: 16, padding: 24, alignItems: 'center', marginBottom: 12, }, resultLabel: { fontSize: 14, color: colors.textInverse, opacity: 0.9 }, resultValue: { fontSize: 42, fontWeight: '800', color: colors.textInverse, marginTop: 8 }, resultUnit: { fontSize: 14, color: colors.textInverse, opacity: 0.9, marginTop: 2 }, cardSubTitleRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 10, }, cardSubTitle: { fontSize: 12, color: colors.textHint }, insuranceRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10, }, insuranceLabel: { flex: 1, fontSize: 14, color: colors.textPrimary }, insuranceAmount: { width: 80, fontSize: 14, fontWeight: '700', color: colors.textPrimary, textAlign: 'right' }, insuranceRate: { width: 60, fontSize: 13, color: colors.textSecondary, textAlign: 'right' }, divider: { height: 1, backgroundColor: colors.borderLight, marginVertical: 10 }, totalRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, totalLabel: { fontSize: 15, fontWeight: '700', color: colors.textPrimary }, totalAmount: { fontSize: 18, fontWeight: '800', color: colors.primary }, taxGrid: { flexDirection: 'row', gap: 12, marginBottom: 10 }, taxItem: { flex: 1, backgroundColor: colors.pageBg, borderRadius: 10, padding: 12, }, taxItemLabel: { fontSize: 12, color: colors.textSecondary, marginBottom: 6 }, taxItemValue: { fontSize: 18, fontWeight: '800', color: colors.textPrimary }, taxRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, }, taxRowLabel: { fontSize: 14, color: colors.textPrimary }, taxRowValue: { fontSize: 15, fontWeight: '700', color: colors.textPrimary }, structureChart: { flexDirection: 'row', alignItems: 'center' }, pie: { width: 100, height: 100, borderRadius: 50, backgroundColor: colors.borderLight, overflow: 'hidden', }, pieSlice: { position: 'absolute', width: 100, height: 100, borderRadius: 50, }, structureLegend: { flex: 1, marginLeft: 20 }, legendRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 10 }, legendDot: { width: 10, height: 10, borderRadius: 5 }, legendLabel: { flex: 1, fontSize: 13, color: colors.textPrimary, marginLeft: 8 }, legendValue: { fontSize: 13, fontWeight: '700', color: colors.textPrimary, marginRight: 8 }, legendPercent: { width: 40, fontSize: 12, color: colors.textSecondary, textAlign: 'right' }, toolRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 12, borderTopWidth: 1, borderTopColor: colors.borderLight, }, toolIcon: { width: 36, height: 36, borderRadius: 18, alignItems: 'center', justifyContent: 'center', }, toolBody: { flex: 1, marginLeft: 12 }, toolTitle: { fontSize: 14, fontWeight: '700', color: colors.textPrimary }, toolDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 2 }, saveBtn: { backgroundColor: colors.primaryBtn, borderRadius: 24, paddingVertical: 14, alignItems: 'center', marginTop: 8, }, saveBtnText: { fontSize: 16, fontWeight: '700', color: colors.white }, });