Files
worth-calculator/components/calculator.tsx
2025-03-19 17:18:56 +08:00

777 lines
38 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { Wallet, Github} from 'lucide-react'; // 保留需要的组件
import Image from 'next/image';
const SalaryCalculator = () => {
// 添加滚动位置保存的引用
const scrollPositionRef = useRef(0);
// 添加自动重定向逻辑
useEffect(() => {
// 在所有环境中执行重定向
if (typeof window !== 'undefined') {
const hostname = window.location.hostname;
if (hostname !== 'jobworth.zippland.com' && hostname !== 'localhost' && !hostname.includes('127.0.0.1')) {
window.location.href = 'https://jobworth.zippland.com' + window.location.pathname;
}
}
}, []);
const [formData, setFormData] = useState({
annualSalary: '', // 年薪
pppFactor: '4.19', // 购买力平价转换因子,默认为中国大陆
country: 'china', // 国家/地区,默认为中国
workDaysPerWeek: '5', // 每周工作天数
wfhDaysPerWeek: '0', // 每周居家办公天数
annualLeave: '5', // 年假天数
paidSickLeave: '12', // 带薪病假天数
publicHolidays: '13', // 法定节假日
workHours: '10', // 工作时长
commuteHours: '2', // 通勤时长
restTime: '2', // 休息时间(午休+摸鱼)
workEnvironment: '1.0', // 工作环境系数
leadership: '1.0', // 领导/老板系数
teamwork: '1.0', // 同事环境系数
degreeType: 'bachelor', // 学位类型,改为本科
schoolType: 'elite', // 学校类型
bachelorType: 'elite', // 新增:本科背景类型
education: '1.2', // 学历系数修改为对应本科985/211的系数
cityFactor: '1.0', // 城市系数,默认为三线城市
homeTown: 'no', // 新增:是否在家乡工作,默认不在
shuttle: '1.0', // 班车系数
canteen: '1.0', // 食堂系数
workYears: '0', // 新增:工作年限
jobStability: 'private' // 新增:工作稳定度/类型
});
// 添加滚动位置保存和恢复逻辑
useEffect(() => {
const handleBeforeStateChange = () => {
// 保存当前滚动位置
if (typeof window !== 'undefined') {
scrollPositionRef.current = window.scrollY;
}
};
const handleAfterStateChange = () => {
// 恢复滚动位置
if (typeof window !== 'undefined') {
setTimeout(() => {
window.scrollTo(0, scrollPositionRef.current);
}, 0);
}
};
// 添加到全局事件
window.addEventListener('beforeStateChange', handleBeforeStateChange);
window.addEventListener('afterStateChange', handleAfterStateChange);
return () => {
// 清理事件监听器
window.removeEventListener('beforeStateChange', handleBeforeStateChange);
window.removeEventListener('afterStateChange', handleAfterStateChange);
};
}, []);
const calculateWorkingDays = useCallback(() => {
const weeksPerYear = 52;
const totalWorkDays = weeksPerYear * Number(formData.workDaysPerWeek); // 确保转换为数字
const totalLeaves = Number(formData.annualLeave) + Number(formData.publicHolidays) + Number(formData.paidSickLeave) * 0.6; // 带薪病假按70%权重计算
return Math.max(totalWorkDays - totalLeaves, 0);
}, [formData.workDaysPerWeek, formData.annualLeave, formData.publicHolidays, formData.paidSickLeave]);
const calculateDailySalary = useCallback(() => {
if (!formData.annualSalary) return 0;
const workingDays = calculateWorkingDays();
// 应用PPP转换因子标准化薪资
// 中国地区直接使用默认值4.19其他地区使用用户输入的PPP
const pppFactor = formData.country === 'china' ? 4.19 : (Number(formData.pppFactor) || 4.19);
const standardizedSalary = Number(formData.annualSalary) * (4.19 / pppFactor);
return standardizedSalary / workingDays; // 除 0 不管, Infinity(爽到爆炸)!
}, [formData.annualSalary, formData.pppFactor, formData.country, calculateWorkingDays]);
// 新增:获取显示用的日薪(转回原始货币)
const getDisplaySalary = useCallback(() => {
const dailySalaryInCNY = calculateDailySalary();
if (formData.country === 'china') {
return dailySalaryInCNY.toFixed(2);
} else {
// 非中国地区,转回本地货币
const pppFactor = Number(formData.pppFactor) || 4.19;
return (dailySalaryInCNY * pppFactor / 4.19).toFixed(2);
}
}, [calculateDailySalary, formData.country, formData.pppFactor]);
const handleInputChange = useCallback((name: string, value: string) => {
// 触发自定义事件,保存滚动位置
if (typeof window !== 'undefined') {
window.dispatchEvent(new Event('beforeStateChange'));
}
// 直接设置值,不进行任何验证
setFormData(prev => ({
...prev,
[name]: value
}));
// 在状态更新后,触发恢复滚动位置事件
setTimeout(() => {
if (typeof window !== 'undefined') {
window.dispatchEvent(new Event('afterStateChange'));
}
}, 0);
}, []);
const calculateValue = () => {
if (!formData.annualSalary) return 0;
const dailySalary = calculateDailySalary();
const workHours = Number(formData.workHours);
const commuteHours = Number(formData.commuteHours);
const restTime = Number(formData.restTime);
// 确保正确转换为数字使用parseFloat可以更可靠地处理字符串转数字
const workDaysPerWeek = parseFloat(formData.workDaysPerWeek) || 5;
// 允许wfhDaysPerWeek为空字符串计算时才处理为0
const wfhInput = formData.wfhDaysPerWeek.trim();
const wfhDaysPerWeek = wfhInput === '' ? 0 : Math.min(parseFloat(wfhInput) || 0, workDaysPerWeek);
// 确保有办公室工作天数时才计算比例否则设为0
const officeDaysRatio = workDaysPerWeek > 0 ? (workDaysPerWeek - wfhDaysPerWeek) / workDaysPerWeek : 0;
// 在计算结果中添加一个小的日志输出,以便调试
console.log('WFH计算:', {
workDaysPerWeek,
wfhDaysPerWeek,
officeDaysRatio,
effectiveCommute: commuteHours * officeDaysRatio
});
// 通勤时间按办公室工作比例计算,并考虑班车因素
const shuttleFactor = Number(formData.shuttle);
const effectiveCommuteHours = commuteHours * officeDaysRatio * shuttleFactor;
// 工作环境因素,包含食堂和家乡因素
const canteenFactor = Number(formData.canteen);
// 在家乡工作有额外加成
const homeTownFactor = formData.homeTown === 'yes' ? 1.4 : 1.0;
const environmentFactor = Number(formData.workEnvironment) *
Number(formData.leadership) *
Number(formData.teamwork) *
Number(formData.cityFactor) *
canteenFactor *
homeTownFactor;
// 根据工作年限计算经验薪资倍数
const workYears = Number(formData.workYears);
let experienceSalaryMultiplier = 1.0;
// 基准薪资增长曲线(适用于私企)
let baseSalaryMultiplier = 1.0;
if (workYears === 0) baseSalaryMultiplier = 1.0; // 应届生基准值
else if (workYears === 1) baseSalaryMultiplier = 1.5; // 1年1.50-2.00,取中间值
else if (workYears <= 3) baseSalaryMultiplier = 2.2; // 2-3年2.20-2.50,取中间值
else if (workYears <= 5) baseSalaryMultiplier = 2.7; // 4-5年2.70-3.00,取中间值
else if (workYears <= 8) baseSalaryMultiplier = 3.2; // 6-8年3.20-3.50,取中间值
else if (workYears <= 10) baseSalaryMultiplier = 3.6; // 9-10年3.60-3.80,取中间值
else baseSalaryMultiplier = 3.9; // 11-13年3.90-4.20,取中间值
// 工作单位类型对涨薪幅度的影响系数
let salaryGrowthFactor = 1.0; // 私企基准
if (formData.jobStability === 'foreign') {
salaryGrowthFactor = 0.8; // 外企涨薪幅度为私企的80%
} else if (formData.jobStability === 'state') {
salaryGrowthFactor = 0.5; // 央/国企涨薪幅度为私企的50%
} else if (formData.jobStability === 'government') {
salaryGrowthFactor = 0.3; // 体制内涨薪幅度为私企的30%
}
// 根据公式: 1 + (对应幅度-1) * 工作单位系数,计算最终薪资倍数
experienceSalaryMultiplier = 1 + (baseSalaryMultiplier - 1) * salaryGrowthFactor;
// 薪资满意度应该受到经验薪资倍数的影响
// 相同薪资,对于高经验者来说价值更低,对应的计算公式需要考虑经验倍数
return (dailySalary * environmentFactor) /
(35 * (workHours + effectiveCommuteHours - 0.5 * restTime) * Number(formData.education) * experienceSalaryMultiplier);
};
const value = calculateValue();
const getValueAssessment = () => {
if (!formData.annualSalary) return { text: "请输入年薪", color: "text-gray-500" };
if (value < 0.6) return { text: "惨绝人寰", color: "text-pink-800" };
if (value < 1.0) return { text: "略惨", color: "text-red-500" };
if (value <= 1.8) return { text: "一般", color: "text-orange-500" };
if (value <= 2.5) return { text: "还不错", color: "text-blue-500" };
if (value <= 3.0) return { text: "很爽", color: "text-green-500" };
if (value <= 3.5) return { text: "爽到爆炸", color: "text-purple-500" };
return { text: "人生巅峰", color: "text-yellow-400" };
};
const RadioGroup = ({ label, name, value, onChange, options }: {
label: string;
name: string;
value: string;
onChange: (name: string, value: string) => void;
options: Array<{ label: string; value: string; }>;
}) => (
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">{label}</label>
<div className="grid grid-cols-4 gap-2">
{options.map((option) => (
<button
key={option.value}
className={`px-3 py-2 rounded-md text-sm transition-colors
${value === option.value
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300 font-medium'
: 'bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:text-gray-300'}`}
onClick={(e) => {
e.preventDefault(); // 阻止默认行为
e.stopPropagation(); // 阻止事件冒泡
onChange(name, option.value);
}}
type="button"
>
{option.label}
</button>
))}
</div>
</div>
);
// 根据学位类型和学校类型计算教育系数
const calculateEducationFactor = useCallback(() => {
const degreeType = formData.degreeType;
const schoolType = formData.schoolType;
const bachelorType = formData.bachelorType;
// 使用更简单的方式计算系数,避免复杂的索引类型问题
let factor = 1.0; // 默认值
// 专科及以下固定为0.8
if (degreeType === 'belowBachelor') {
factor = 0.8;
}
// 本科学历
else if (degreeType === 'bachelor') {
if (schoolType === 'secondTier') factor = 0.9; // 二本三本
else if (schoolType === 'firstTier') factor = 1.0; // 双非/QS100/USnews50
else if (schoolType === 'elite') factor = 1.2; // 985/211/QS30/USnews20
}
// 硕士学历 - 考虑本科背景
else if (degreeType === 'masters') {
// 先获取本科背景的基础系数
let bachelorBaseCoefficient = 0;
if (bachelorType === 'secondTier') bachelorBaseCoefficient = 0.9; // 二本三本
else if (bachelorType === 'firstTier') bachelorBaseCoefficient = 1.0; // 双非/QS100/USnews50
else if (bachelorType === 'elite') bachelorBaseCoefficient = 1.2; // 985/211/QS30/USnews20
// 再计算硕士学校的加成系数
let mastersBonus = 0;
if (schoolType === 'secondTier') mastersBonus = 0.4; // 二本三本硕士
else if (schoolType === 'firstTier') mastersBonus = 0.5; // 双非/QS100/USnews50硕士
else if (schoolType === 'elite') mastersBonus = 0.6; // 985/211/QS30/USnews20硕士
// 最终学历系数 = 本科基础 + 硕士加成
factor = bachelorBaseCoefficient + mastersBonus;
}
// 博士学历
else if (degreeType === 'phd') {
if (schoolType === 'secondTier') factor = 1.6; // 二本三本博士
else if (schoolType === 'firstTier') factor = 1.8; // 双非/QS100/USnews50博士
else if (schoolType === 'elite') factor = 2.0; // 985/211/QS30/USnews20博士
}
// 更新education字段
if (formData.education !== String(factor)) {
// 这里不使用handleInputChange以避免触发滚动保存/恢复逻辑
setFormData(prev => ({
...prev,
education: String(factor)
}));
}
return factor;
}, [formData.degreeType, formData.schoolType, formData.bachelorType, formData.education]);
// 在组件初始化和学历选择变化时计算教育系数
useEffect(() => {
calculateEducationFactor();
}, [formData.degreeType, formData.schoolType, calculateEducationFactor]);
return (
<div className="max-w-4xl mx-auto p-4 space-y-8 text-gray-900 dark:text-white">
<div className="text-center space-y-2">
<h1 className="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-purple-600 dark:from-blue-400 dark:to-purple-400">
b班上得值不值·
<span className="ml-2 text-xs align-top text-gray-500 dark:text-gray-400">v3.4.1</span>
</h1>
{/* GitHub 链接和访问量计数 */}
<div className="flex flex-col items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
{/* 第一排: GitHub、Email、小红书 */}
<div className="flex items-center justify-center gap-4">
<a
href="https://github.com/zippland/worth-calculator"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
>
<Github className="w-4 h-4" />
<span className="hidden sm:inline">Star on</span>
<span>GitHub</span>
</a>
<div className="w-px h-4 bg-gray-300 dark:bg-gray-700"></div>
<a
href="mailto:zylanjian@outlook.com"
className="flex items-center gap-1.5 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg>
<span>Email</span>
</a>
<div className="w-px h-4 bg-gray-300 dark:bg-gray-700"></div>
<a
href="https://www.xiaohongshu.com/user/profile/623e8b080000000010007721?xsec_token=YBdeHZTp_aVwi1Ijmras5CgTN9fhmJ9fwVRviTyiF_EAs%3D&xsec_source=app_share&xhsshare=CopyLink&appuid=623e8b080000000010007721&apptime=1742023486&share_id=48e7c11a2abe404494693a24218213ae&share_channel=copy_link"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-4 h-4">
<path d="M12 22c5.421 0 10-4.579 10-10S17.421 2 12 2 2 6.579 2 12s4.579 10 10 10zm0-18c4.337 0 8 3.663 8 8s-3.663 8-8 8-8-3.663-8-8 3.663-8 8-8z"/>
<path d="M17 8.4c0-1.697-1.979-2.709-3.489-1.684-1.628-1.028-3.639.045-3.511 1.68-1.579-.104-2.702 1.74-1.614 3.079-1.191.974-.401 3.062 1.394 3.062.966 1.269 2.902.941 3.614-.335 1.53.503 3.204-.812 2.604-2.802 1.468-.572.905-2.749-.998-3.001z"/>
</svg>
<span></span>
</a>
</div>
{/* 第二排: "持续更新中..."和欢迎建议文字 */}
<div className="flex items-center gap-2">
<span className="text-blue-500 dark:text-blue-400 font-medium">
<span className="animate-pulse"></span>
<span className="animate-pulse"></span>
</span>
</div>
{/* 第三排: 访问量 */}
<a
href="https://hits.seeyoufarm.com"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5"
>
<Image
src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FYourUsername%2Fworth-calculator&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=visits&edge_flat=true"
alt="访问量"
width={100}
height={20}
className="h-5 w-auto"
unoptimized
/>
</a>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-xl shadow-gray-200/50 dark:shadow-black/30">
<div className="p-6 space-y-8">
{/* 薪资与工作时间 section */}
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{formData.country === 'china' ? '年薪总包(元)' : '年薪总包(当地货币)'}
</label>
<div className="flex items-center gap-2 mt-1">
<Wallet className="w-4 h-4 text-gray-500 dark:text-gray-400" />
<input
type="number"
value={formData.annualSalary}
onChange={(e) => handleInputChange('annualSalary', e.target.value)}
placeholder={formData.country === 'china' ? "税前年薪" : "使用当地货币"}
className="block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
</div>
<div className="flex items-center mt-2">
<input
id="non-china"
type="checkbox"
checked={formData.country !== 'china'}
onChange={(e) => handleInputChange('country', e.target.checked ? 'other' : 'china')}
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<label htmlFor="non-china" className="ml-2 text-xs text-gray-500 dark:text-gray-400">
</label>
</div>
</div>
{formData.country === 'other' && (
<div className="relative">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
(PPP)
<span className="ml-1 inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-300 cursor-pointer group relative">
?
<span className="absolute z-10 invisible group-hover:visible bg-gray-900 text-white text-xs rounded py-1 px-2 bottom-full mb-1 left-1/2 transform -translate-x-1/2 w-48 sm:w-64">
PPP转换因子是将各国货币购买力标准化的指标4.1914.19
</span>
</span>
</label>
<input
type="number"
step="0.01"
value={formData.pppFactor}
onChange={(e) => handleInputChange('pppFactor', e.target.value)}
placeholder="请输入购买力平价转换因子"
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
中国大陆:4.19, 日本:102.59, 美国:1.00, 新加坡:0.84
<a
href="https://zh.wikipedia.org/wiki/%E8%B4%AD%E4%B9%B0%E5%8A%9B%E5%B9%B3%E4%BB%B7%E8%BD%AC%E6%8D%A2%E5%9B%A0%E5%AD%90"
target="_blank"
rel="noopener noreferrer"
className="ml-1 text-blue-500 hover:underline"
>
</a>
</p>
</div>
)}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">/d</label>
<input
type="number"
value={formData.workDaysPerWeek}
onChange={(e) => handleInputChange('workDaysPerWeek', e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
wfh天数/d
<span className="ml-1 inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-300 cursor-pointer group relative">
?
<span className="absolute z-10 invisible group-hover:visible bg-gray-900 text-white text-xs rounded py-1 px-2 bottom-full mb-1 left-1/2 transform -translate-x-1/2 w-48 sm:w-64">
WFH指居家办公(Work From Home)
</span>
</span>
</label>
<input
type="number"
min="0"
max={formData.workDaysPerWeek}
step="1"
value={formData.wfhDaysPerWeek}
onChange={(e) => handleInputChange('wfhDaysPerWeek', e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">/d</label>
<input
type="number"
value={formData.annualLeave}
onChange={(e) => handleInputChange('annualLeave', e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">/d</label>
<input
type="number"
value={formData.publicHolidays}
onChange={(e) => handleInputChange('publicHolidays', e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">/d</label>
<input
type="number"
value={formData.paidSickLeave}
onChange={(e) => handleInputChange('paidSickLeave', e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
/h
<span className="ml-1 inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-300 cursor-pointer group relative">
?
<span className="absolute z-10 invisible group-hover:visible bg-gray-900 text-white text-xs rounded py-1 px-2 bottom-full mb-1 left-1/2 transform -translate-x-1/2 w-48 sm:w-64">
&quot;-&quot;
</span>
</span>
</label>
<input
type="number"
value={formData.workHours}
onChange={(e) => handleInputChange('workHours', e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
/h
<span className="ml-1 inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-300 cursor-pointer group relative">
?
<span className="absolute z-10 invisible group-hover:visible bg-gray-900 text-white text-xs rounded py-1 px-2 bottom-full mb-1 left-1/2 transform -translate-x-1/2 w-48 sm:w-64">
</span>
</span>
</label>
<input
type="number"
value={formData.commuteHours}
onChange={(e) => handleInputChange('commuteHours', e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">&/h</label>
<input
type="number"
value={formData.restTime}
onChange={(e) => handleInputChange('restTime', e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
/>
</div>
</div>
</div>
<div className="border-t border-gray-200 dark:border-gray-700 my-6"></div>
{/* 环境系数 */}
<div className="space-y-4">
{/* 添加工作类型RadioGroup */}
<RadioGroup
label="合同类型"
name="jobStability"
value={formData.jobStability}
onChange={handleInputChange}
options={[
{ label: '私企续签', value: 'private' },
{ label: '外企续签', value: 'foreign' },
{ label: '长期雇佣', value: 'state' },
{ label: '永久编制', value: 'government' },
]}
/>
<RadioGroup
label="工作环境"
name="workEnvironment"
value={formData.workEnvironment}
onChange={handleInputChange}
options={[
{ label: '偏僻的工厂/工地/户外', value: '0.8' },
{ label: '工厂/工地/户外', value: '0.9' },
{ label: '普通环境', value: '1.0' },
{ label: 'CBD', value: '1.1' },
]}
/>
<RadioGroup
label="所在城市(按生活成本选择)"
name="cityFactor"
value={formData.cityFactor}
onChange={handleInputChange}
options={[
{ label: '一线城市', value: '0.70' },
{ label: '新一线', value: '0.80' },
{ label: '二线城市', value: '1.0' },
{ label: '三线城市', value: '1.10' },
{ label: '四线城市', value: '1.25' },
{ label: '县城', value: '1.40' },
{ label: '乡镇', value: '1.50' },
]}
/>
<RadioGroup
label="是否在家乡工作"
name="homeTown"
value={formData.homeTown}
onChange={handleInputChange}
options={[
{ label: '不在家乡', value: 'no' },
{ label: '在家乡', value: 'yes' },
]}
/>
<RadioGroup
label="领导/老板"
name="leadership"
value={formData.leadership}
onChange={handleInputChange}
options={[
{ label: '对我不爽', value: '0.7' },
{ label: '管理严格', value: '0.9' },
{ label: '中规中矩', value: '1.0' },
{ label: '善解人意', value: '1.1' },
{ label: '我是嫡系', value: '1.3' },
]}
/>
<RadioGroup
label="同事环境"
name="teamwork"
value={formData.teamwork}
onChange={handleInputChange}
options={[
{ label: '都是傻逼', value: '0.9' },
{ label: '萍水相逢', value: '1.0' },
{ label: '和和睦睦', value: '1.1' },
{ label: '私交甚好', value: '1.2' },
]}
/>
<RadioGroup
label="班车服务(加分项)"
name="shuttle"
value={formData.shuttle}
onChange={handleInputChange}
options={[
{ label: '无班车', value: '1.0' },
{ label: '班车不便', value: '0.9' },
{ label: '便利班车', value: '0.7' },
{ label: '班车直达', value: '0.5' },
]}
/>
<RadioGroup
label="食堂情况(加分项)"
name="canteen"
value={formData.canteen}
onChange={handleInputChange}
options={[
{ label: '无食堂/很难吃', value: '1.0' },
{ label: '食堂一般', value: '1.05' },
{ label: '食堂不错', value: '1.1' },
{ label: '食堂超赞', value: '1.15' },
]}
/>
{/* 学历和工作年限 */}
<div className="space-y-4">
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300"></label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1"></label>
<select
value={formData.degreeType}
onChange={(e) => handleInputChange('degreeType', e.target.value)}
className="block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
>
<option value="belowBachelor"></option>
<option value="bachelor"></option>
<option value="masters"></option>
<option value="phd"></option>
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1"></label>
<select
value={formData.schoolType}
onChange={(e) => handleInputChange('schoolType', e.target.value)}
className="block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
disabled={formData.degreeType === 'belowBachelor'}
>
<option value="secondTier"></option>
{formData.degreeType === 'bachelor' ? (
<>
<option value="firstTier">/ QS200/ USnews80</option>
<option value="elite">985211/ QS50/ USnews30</option>
</>
) : (
<>
<option value="firstTier">/ QS100/ USnews50</option>
<option value="elite">985211/ QS30/ USnews20</option>
</>
)}
</select>
</div>
</div>
{/* 硕士显示本科背景选项 */}
{formData.degreeType === 'masters' && (
<div className="mt-4">
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1"></label>
<select
value={formData.bachelorType}
onChange={(e) => handleInputChange('bachelorType', e.target.value)}
className="block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
>
<option value="secondTier"></option>
<option value="firstTier">/ QS200/ USnews80</option>
<option value="elite">985211/ QS50/ USnews30</option>
</select>
</div>
)}
</div>
{/* 工作年限选择 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300"></label>
<select
value={formData.workYears}
onChange={(e) => handleInputChange('workYears', e.target.value)}
className="block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 dark:text-white"
>
<option value="0"></option>
<option value="1">1-3</option>
<option value="2">3-5</option>
<option value="4">5-8</option>
<option value="6">8-10</option>
<option value="10">10-12</option>
<option value="15">12</option>
</select>
</div>
</div>
</div>
</div>
</div>
{/* 结果卡片优化 */}
<div className="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 rounded-xl p-6 shadow-inner">
<div className="grid grid-cols-3 gap-8">
<div>
<div className="text-sm font-medium text-gray-500 dark:text-gray-400"></div>
<div className="text-2xl font-semibold mt-1 text-gray-900 dark:text-white">{calculateWorkingDays()}</div>
</div>
<div>
<div className="text-sm font-medium text-gray-500 dark:text-gray-400"></div>
<div className="text-2xl font-semibold mt-1 text-gray-900 dark:text-white">
{formData.country === 'china' ? '¥' : '$'}{getDisplaySalary()}
</div>
</div>
<div>
<div className="text-sm font-medium text-gray-500 dark:text-gray-400"></div>
<div className={`text-2xl font-semibold mt-1 ${getValueAssessment().color}`}>
{value.toFixed(2)}
<span className="text-base ml-2">({getValueAssessment().text})</span>
</div>
</div>
</div>
</div>
</div>
);
};
export default SalaryCalculator;