Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/share/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ function ShareCardWrapper() {
const homeTown = searchParams.get('homeTown') || 'no';
const shuttle = searchParams.get('shuttle') || '1.0';
const canteen = searchParams.get('canteen') || '1.0';
const climate = searchParams.get('climate') || '1.0';
const hasShuttle = searchParams.get('hasShuttle') === 'true';
const hasCanteen = searchParams.get('hasCanteen') === 'true';
const hasClimate = searchParams.get('hasClimate') === 'true';

// 额外参数 - 学历和工作经验
const degreeType = searchParams.get('degreeType') || 'bachelor';
Expand Down Expand Up @@ -85,8 +87,10 @@ function ShareCardWrapper() {
homeTown={homeTown}
shuttle={shuttle}
canteen={canteen}
climate={climate}
hasShuttle={hasShuttle}
hasCanteen={hasCanteen}
hasClimate={hasClimate}

// 学历和工作经验
degreeType={degreeType}
Expand Down
20 changes: 19 additions & 1 deletion components/LanguageContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,12 @@ const translations: Record<Language, Record<string, string>> = {
'canteen_average': '食堂一般',
'canteen_good': '食堂不错',
'canteen_excellent': '食堂超赞',

'climate': '气候情况(加分项)',
'climate_none': '气候恶劣',
'climate_average': '气候一般',
'climate_good': '舒适宜人',
'climate_excellent': '度假天堂',

// 教育和工作经验
'education_level': '个人学历水平',
'degree_type': '学位类型',
Expand Down Expand Up @@ -722,6 +727,7 @@ const translations: Record<Language, Record<string, string>> = {
'share_leadership_relation': '领导关系',
'share_colleague_relationship': '同事关系',
'share_canteen_quality': '食堂情况',
'share_climate_quality': '气候情况',
'share_education_and_experience': '教育与工作经验',
'share_highest_degree': '最高学历',
'share_school_type_label': '学校类型',
Expand Down Expand Up @@ -897,6 +903,11 @@ const translations: Record<Language, Record<string, string>> = {
'canteen_average': 'Average Quality',
'canteen_good': 'Good Quality',
'canteen_excellent': 'Excellent Quality',
'climate': 'Climate Quality (Bonus)',
'climate_none': 'Unbearable',
'climate_average': 'Decent',
'climate_good': 'Comfortable and Pleasant',
'climate_excellent': 'Absolutely Delightful',

// Education and work experience
'education_level': 'Education Level',
Expand Down Expand Up @@ -960,6 +971,7 @@ const translations: Record<Language, Record<string, string>> = {
'share_leadership_relation': 'Leadership',
'share_colleague_relationship': 'Colleague Relations',
'share_canteen_quality': 'Canteen',
'share_climate_quality': 'Climate',
'share_education_and_experience': 'Education & Experience',
'share_highest_degree': 'Highest Degree',
'share_school_type_label': 'School Type',
Expand Down Expand Up @@ -1135,6 +1147,11 @@ const translations: Record<Language, Record<string, string>> = {
'canteen_average': '普通',
'canteen_good': '美味しい',
'canteen_excellent': '非常に美味しい',
'climate': '気候(加点項目)',
'climate_none': '過酷で居心地が悪い',
'climate_average': 'まあまあ、可もなく不可もなく',
'climate_good': '過ごしやすくて快適',
'climate_excellent': 'まるでリゾートのように快適',

// 教育と経験
'education_level': '学歴',
Expand Down Expand Up @@ -1198,6 +1215,7 @@ const translations: Record<Language, Record<string, string>> = {
'share_leadership_relation': '上司との関係',
'share_colleague_relationship': '同僚との関係',
'share_canteen_quality': '社員食堂',
'share_climate_quality': '気候',
'share_education_and_experience': '学歴と経験',
'share_highest_degree': '最終学歴',
'share_school_type_label': '大学タイプ',
Expand Down
35 changes: 29 additions & 6 deletions components/ShareCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface ShareCardProps {
homeTown: string;
shuttle: string;
canteen: string;
climate: string;

// 学历和工作经验
degreeType: string;
Expand All @@ -50,6 +51,7 @@ interface ShareCardProps {
// 新增属性
hasShuttle: boolean;
hasCanteen: boolean;
hasClimate: boolean;
}

// 将中文评级转换为翻译键
Expand Down Expand Up @@ -145,6 +147,15 @@ const getCanteenDesc = (canteen: string, t: (key: string) => string): string =>
return t('canteen_none');
};

// 获取气候情况描述
const getClimateDesc = (climate: string, t: (key: string) => string): string => {
if (climate === '1.0') return t('climate_none');
else if (climate === '1.05') return t('climate_average');
else if (climate === '1.1') return t('climate_good');
else if (climate === '1.15') return t('climate_excellent');
return t('climate_none');
};

// 获取合同类型描述
const getJobStabilityDesc = (type: string, t: (key: string) => string): string => {
if (type === 'private') return t('job_private');
Expand Down Expand Up @@ -287,16 +298,22 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
} else {
cityComment += " " + t('share_not_hometown_comment');
}

const cityDetails = [
{ label: t('share_work_city'), value: cityName },
{ label: t('share_is_hometown'), value: isHomeTown ? t('share_yes') : t('share_no') },
{ label: t('share_country'), value: getCountryName(props.countryCode, language) }
];

if (props.hasClimate) {
cityDetails.push({ label: t('share_climate_quality'), value: getClimateDesc(props.climate, t) });
}

comments.push({
title: t('share_work_city'),
content: cityComment,
emoji: isHomeTown ? "🏡" : "🌆",
details: [
{ label: t('share_work_city'), value: cityName },
{ label: t('share_is_hometown'), value: isHomeTown ? t('share_yes') : t('share_no') },
{ label: t('share_country'), value: getCountryName(props.countryCode, language) }
]
details: cityDetails
});

// 3. 通勤与WFH评价
Expand Down Expand Up @@ -392,7 +409,7 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
if (props.hasCanteen) {
environmentDetails.push({ label: t('share_canteen_quality'), value: getCanteenDesc(props.canteen, t) });
}

comments.push({
title: t('share_work_environment_title'),
content: environmentComment,
Expand Down Expand Up @@ -801,6 +818,12 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
<span className="font-medium text-gray-800">{getCanteenDesc(props.canteen, t)}</span>
</div>
)}
{props.hasClimate && (
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_climate_quality')}</span>
<span className="font-medium text-gray-800">{getClimateDesc(props.climate, t)}</span>
</div>
)}
</div>
</div>

Expand Down
71 changes: 67 additions & 4 deletions components/calculator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Link from 'next/link'; // 导入Link组件用于导航
import { useLanguage } from './LanguageContext';
import { LanguageSwitcher } from './LanguageSwitcher';
import { countryNames } from './LanguageContext';
import {type} from "node:os";

// 定义PPP转换因子映射表
const pppFactors: Record<string, number> = {
Expand Down Expand Up @@ -334,11 +335,13 @@ interface HistoryItem {
homeTown: string;
shuttle: string;
canteen: string;
climate: string;
workYears: string;
jobStability: string;
bachelorType: string;
hasShuttle: boolean;
hasCanteen: boolean;
hasClimate: boolean;
}

// 定义表单数据接口
Expand All @@ -364,10 +367,12 @@ interface FormData {
workYears: string;
shuttle: string;
canteen: string;
climate: string
jobStability: string;
education: string;
hasShuttle: boolean;
hasCanteen: boolean;
hasClimate: boolean;
}

// 定义计算结果接口
Expand Down Expand Up @@ -432,10 +437,12 @@ const SalaryCalculator = () => {
workYears: '0',
shuttle: '1.0',
canteen: '1.0',
climate: '1.0',
jobStability: 'private', // 新增:工作稳定度/类型
education: '1.0',
hasShuttle: false, // 确保这是一个明确的布尔值
hasCanteen: false, // 确保这是一个明确的布尔值
hasClimate: false,
});

const [showPPPInput, setShowPPPInput] = useState(false);
Expand Down Expand Up @@ -508,12 +515,14 @@ const SalaryCalculator = () => {
homeTown: item.homeTown || formData.homeTown,
shuttle: item.shuttle || formData.shuttle,
canteen: item.canteen || formData.canteen,
climate: item.climate || formData.climate,
workYears: item.workYears || formData.workYears,
jobStability: item.jobStability || formData.jobStability,
bachelorType: item.bachelorType || formData.bachelorType,
// 确保 hasShuttle 和 hasCanteen 有合法的布尔值,即使历史记录中没有这些字段
hasShuttle: typeof item.hasShuttle === 'boolean' ? item.hasShuttle : false,
hasCanteen: typeof item.hasCanteen === 'boolean' ? item.hasCanteen : false,
hasClimate: typeof item.hasClimate === 'boolean' ? item.hasClimate : false,
}));

setHistory(normalizedHistory);
Expand Down Expand Up @@ -633,6 +642,17 @@ const SalaryCalculator = () => {
}, [calculateDailySalary, selectedCountry]);

const handleInputChange = useCallback((name: string, value: string | boolean) => {
if (typeof value === 'string') {
// 非负验证:仅对数字字段进行,允许为空字符串
const numericFields = ['salary', 'workHours', 'commuteHours', 'restTime', 'annualLeave', 'paidSickLeave', 'publicHolidays', 'workDaysPerWeek', 'wfhDaysPerWeek'];
if (numericFields.includes(name) && value !== '') {
const number = parseFloat(value);
if (isNaN(number) || number < 0) {
return; // 忽略非法负数输入
}
}
}

// 触发自定义事件,保存滚动位置
if (typeof window !== 'undefined') {
window.dispatchEvent(new Event('beforeStateChange'));
Expand Down Expand Up @@ -684,13 +704,17 @@ const SalaryCalculator = () => {

// 食堂系数只在勾选时使用,否则为1.0
const canteenFactor = formData.hasCanteen ? Number(formData.canteen) : 1.0;

// 气候系数只在勾选时使用,否则为1.0
const climateFactor = formData.hasClimate ? Number(formData.climate) : 1.0;

// 工作环境因素,包含食堂和家乡因素
const environmentFactor = Number(formData.workEnvironment) *
Number(formData.leadership) *
Number(formData.teamwork) *
Number(formData.cityFactor) *
canteenFactor;
canteenFactor *
climateFactor;

// 根据工作年限计算经验薪资倍数
const workYears = Number(formData.workYears);
Expand Down Expand Up @@ -891,11 +915,13 @@ const SalaryCalculator = () => {
homeTown: formData.homeTown,
shuttle: formData.hasShuttle ? formData.shuttle : '1.0',
canteen: formData.hasCanteen ? formData.canteen : '1.0',
climate: formData.hasClimate ? formData.climate : '1.0',
workYears: formData.workYears,
jobStability: formData.jobStability,
bachelorType: formData.bachelorType,
hasShuttle: formData.hasShuttle,
hasCanteen: formData.hasCanteen,
hasClimate: formData.hasClimate,
};

try {
Expand All @@ -908,7 +934,7 @@ const SalaryCalculator = () => {
}

return newHistoryItem;
}, [formData, value, getValueAssessmentKey, getValueAssessment, selectedCountry, history, getCountryName, calculateWorkingDays, getDisplaySalary, formData.hasShuttle, formData.hasCanteen]);
}, [formData, value, getValueAssessmentKey, getValueAssessment, selectedCountry, history, getCountryName, calculateWorkingDays, getDisplaySalary, formData.hasShuttle, formData.hasCanteen, formData.hasClimate]);

// 删除单条历史记录
const deleteHistoryItem = useCallback((id: string, e: React.MouseEvent) => {
Expand Down Expand Up @@ -1073,12 +1099,14 @@ const SalaryCalculator = () => {
homeTown: item.homeTown,
shuttle: item.shuttle,
canteen: item.canteen,
climate: item.climate,
workYears: item.workYears,
jobStability: item.jobStability,
bachelorType: item.bachelorType,
// 确保 hasShuttle 和 hasCanteen 有合法的布尔值
hasShuttle: typeof item.hasShuttle === 'boolean' ? item.hasShuttle : false,
hasCanteen: typeof item.hasCanteen === 'boolean' ? item.hasCanteen : false,
hasClimate: typeof item.hasClimate === 'boolean' ? item.hasClimate : false,
});

// 设置国家
Expand Down Expand Up @@ -1122,13 +1150,15 @@ const SalaryCalculator = () => {
homeTown: item.homeTown,
shuttle: item.shuttle,
canteen: item.canteen,
climate: item.climate,
workYears: item.workYears,
jobStability: item.jobStability,
bachelorType: item.bachelorType,
countryCode: item.countryCode,
countryName: getCountryName(item.countryCode),
hasShuttle: item.hasShuttle,
hasCanteen: item.hasCanteen,
hasClimate: item.hasClimate,
}
}}
className="text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 p-1.5 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded"
Expand Down Expand Up @@ -1576,15 +1606,46 @@ const SalaryCalculator = () => {
/>
)}
</div>

<div className="space-y-2">
<div className="flex items-center mb-2">
<input
id="hasClimate"
type="checkbox"
checked={formData.hasClimate === true}
onChange={(e) => handleInputChange('hasClimate', e.target.checked)}
className="h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500"
/>
<label htmlFor="hasClimate" className="ml-2 text-sm font-medium text-gray-700 dark:text-gray-300">
{t('climate')}
</label>
</div>

{formData.hasClimate && (
<RadioGroup
label=""
name="climate"
value={formData.climate}
onChange={handleInputChange}
options={[
{ label: t('climate_none'), value: '1.0' },
{ label: t('climate_average'), value: '1.05' },
{ label: t('climate_good'), value: '1.1' },
{ label: t('climate_excellent'), value: '1.15' },
]}
/>
)}
</div>
</div>
</div>
</div>

{/* 结果卡片优化 */}
<div ref={shareResultsRef} 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 ref={shareResultsRef}
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">{t('working_days_per_year')}</div>
<div className="text-sm font-medium text-gray-500 dark:text-gray-400">{t('working_days_per_year')}</div>
<div className="text-2xl font-semibold mt-1 text-gray-900 dark:text-white">{calculateWorkingDays()}{t('days_unit')}</div>
</div>
<div>
Expand Down Expand Up @@ -1632,6 +1693,7 @@ const SalaryCalculator = () => {
homeTown: formData.homeTown,
shuttle: formData.hasShuttle ? formData.shuttle : '1.0',
canteen: formData.hasCanteen ? formData.canteen : '1.0',
climate: formData.hasClimate ? formData.climate : '1.0',
workYears: formData.workYears,
jobStability: formData.jobStability,
bachelorType: formData.bachelorType,
Expand All @@ -1640,6 +1702,7 @@ const SalaryCalculator = () => {
currencySymbol: getCurrencySymbol(selectedCountry),
hasShuttle: formData.hasShuttle,
hasCanteen: formData.hasCanteen,
hasClimate: formData.hasClimate,
}
}}
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors
Expand Down