Merge branch 'main' into feature/history

This commit is contained in:
Zylan
2025-04-01 11:31:38 +08:00
committed by GitHub
9 changed files with 1586 additions and 639 deletions

174
README.md
View File

@@ -1,38 +1,55 @@
# Worth Calculator | 工作性价比计算器
<div align="center">
<img src="title.png" alt="Job Worth Calculator" width="500" />
<br><br>
<a href="https://trendshift.io/repositories/13145" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13145" alt="Zippland%2Fworth-calculator | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
**[English](#job-worth-calculator) | [中文](#工作性价比计算器-1)**
<p>
<img src="https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white" alt="Next.js" />
<img src="https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB" alt="React" />
<img src="https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white" alt="Tailwind CSS" />
<img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
</p>
<p>Calculating the actual value of your job beyond just salary</p>
<p>计算薪资之外的工作真实价值</p>
<p>給料だけでなく、仕事の本当の価値を計算する</p>
<div align="center">
**[⚡ Try it now ⚡](https://worthjob.zippland.com)**
</div>
<p>
<a href="#english"><img src="https://img.shields.io/badge/English-blue?style=for-the-badge" alt="English" /></a>
&nbsp;&nbsp;
<a href="#中文"><img src="https://img.shields.io/badge/中文-red?style=for-the-badge" alt="中文" /></a>
&nbsp;&nbsp;
<a href="#japanese"><img src="https://img.shields.io/badge/日本語-green?style=for-the-badge" alt="日本語" /></a>
</p>
</div>
---
## 🌟 Job Worth Calculator
<div id="english">
A clean, comprehensive tool for calculating the actual value of your job considering multiple factors beyond just salary.
<h2 align="center">📊 Job Worth Calculator</h2>
<p align="center"><i>Calculate how much your job is really worth considering all factors</i></p>
### ✨ Features
- **Comprehensive Evaluation**: Calculate job worth based on salary, work hours, commute time, environment, and more
- **PPP Conversion**: International salary comparison with Purchasing Power Parity conversion across 190+ countries
- **Personal Factors**: Customize calculations with personal education level, work experience, and more
- **Detailed Report**: Generate a shareable, downloadable job analysis report
- **Internationalization**: Available in both English and Chinese
- **Mobile Friendly**: Responsive design works on all devices
### 🚀 Live Demo
Visit [worthjob.zippland.com](https://worthjob.zippland.com) to try it out!
### 🛠️ Technologies
- Next.js
- React
- Tailwind CSS
- TypeScript
- **💰 Comprehensive Evaluation**: Calculate job worth based on salary, work hours, commute time, environment, and more
- **🌏 PPP Conversion**: International salary comparison with Purchasing Power Parity conversion across 190+ countries
- **👩‍🎓 Personal Factors**: Customize calculations with personal education level, work experience, and more
- **📱 Detailed Report**: Generate a shareable, downloadable job analysis report
- **🌐 Internationalization**: Available in both English and Chinese
- **📱 Mobile Friendly**: Responsive design works on all devices
### 🖥️ How to Use
@@ -53,41 +70,38 @@ The job worth score is calculated using a comprehensive formula that accounts fo
- Educational qualification premiums
- Experience-based expectations
### 👨‍💻 Contributing
Contributions are welcome! Here's how you can help:
- [Open an issue](https://github.com/zippland/worth-calculator/issues/new) if you have suggestions or find a bug
- Fork the repository and submit a PR for new features or bug fixes
- Improve documentation or translations
Please make sure to test your changes before submitting a PR.
### 📝 License
[MIT License](LICENSE)
</div>
- [ ] 添加更多工作类型模板
- [ ] 支持数据导出功能
- [x] 添加历史记录功能
- [x] 支持多语言
- [ ] 添加更多可视化图表
---
<div id="中文">
## 🌟 工作性价比计算器
<h2 align="center">📊 工作性价比计算器</h2>
一款简洁、全面的工具,用于计算工作的实际价值,考虑了薪资以外的多种因素。
<p align="center"><i>全面考量各种因素,算一算你的工作到底值不值</i></p>
### ✨ 特点
- **全面评估**: 基于薪资、工作时间、通勤时间、工作环境等多方面因素计算工作价值
- **PPP转换**: 通过购买力平价(PPP)转换支持190多个国家的薪资比较
- **个人因素**: 根据个人学历、工作经验等定制计算
- **详细报告**: 生成可分享、可下载的工作分析报告
- **国际化**: 支持中英文双语
- **移动友好**: 响应式设计,适用于所有设备
### 🚀 在线演示
访问 [worthjob.zippland.com](https://worthjob.zippland.com) 立即试用!
### 🛠️ 技术栈
- Next.js
- React
- Tailwind CSS
- TypeScript
- **💰 全面评估**: 基于薪资、工作时间、通勤时间、工作环境等多方面因素计算工作价值
- **🌏 PPP转换**: 通过购买力平价(PPP)转换支持190多个国家的薪资比较
- **👩‍🎓 个人因素**: 根据个人学历、工作经验等定制计算
- **📱 详细报告**: 生成可分享、可下载的工作分析报告
- **🌐 国际化**: 支持中英文双语
- **📱 移动友好**: 响应式设计,适用于所有设备
### 🖥️ 使用方法
@@ -108,6 +122,70 @@ The job worth score is calculated using a comprehensive formula that accounts fo
- 学历加成
- 基于经验的期望值调整
### 👨‍💻 贡献指南
欢迎参与贡献!以下是您可以提供帮助的方式:
- 如有建议或发现错误,请[提交问题](https://github.com/zippland/worth-calculator/issues/new)
- 分叉仓库并提交PR增加新功能或修复bug
- 改进文档或翻译
请确保在提交PR前测试您的更改。
### 📝 许可证
[MIT 许可证](LICENSE)
[MIT 许可证](LICENSE)
</div>
---
<div id="japanese">
<h2 align="center">📊 仕事の価値計算機</h2>
<p align="center"><i>あなたの仕事が本当にどれだけの価値があるか、様々な要素を考慮して計算します</i></p>
### ✨ 特徴
- **💰 総合的な評価**: 給与、労働時間、通勤時間、職場環境など複数の要素に基づいて仕事の価値を計算
- **🌏 PPP変換**: 購買力平価(PPP)による190カ国以上の国際的な給与比較
- **👩‍🎓 個人要素**: 学歴、職務経験などに基づいてカスタマイズされた計算
- **📱 詳細レポート**: 共有可能でダウンロード可能な仕事分析レポートの生成
- **🌐 多言語対応**: 英語、中国語、日本語で利用可能
- **📱 モバイル対応**: すべてのデバイスで動作するレスポンシブデザイン
### 🖥️ 使用方法
1. 年収を入力
2. 国/地域を選択
3. 勤務詳細(週あたりの勤務日数、勤務時間、通勤時間など)を入力
4. 環境要素(都市、職場環境、チームなど)を指定
5. 学歴と経験を入力
6. 仕事の価値スコアと詳細評価を確認
7. 共有可能なレポートを生成
### 📊 計算方法
仕事の価値スコアは以下を考慮した総合的な計算式を使用しています:
- 標準化された日給PPPで調整済み
- ワークライフバランス要素(労働時間、通勤、リモートワークオプション)
- 環境的側面(オフィスの場所、チームダイナミクス)
- 教育資格による優遇
- 経験に基づく期待値
### 👨‍💻 貢献方法
貢献は大歓迎です!以下の方法でご協力いただけます:
- 提案やバグを発見した場合は[問題を報告](https://github.com/zippland/worth-calculator/issues/new)してください
- リポジトリをフォークし、新機能やバグ修正のためのPRを提出
- ドキュメントや翻訳の改善
PRを提出する前に変更をテストしてください。
### 📝 ライセンス
[MITライセンス](LICENSE)
</div>

View File

@@ -16,8 +16,8 @@ const geistMono = localFont({
export const metadata: Metadata = {
title: {
default: "这b班上得值不值",
template: "%s | 这b班上得值不值"
default: "Is My Job Worth It?",
template: "%s | Is My Job Worth It?"
},
alternates: {
languages: {
@@ -25,7 +25,7 @@ export const metadata: Metadata = {
"zh-CN": "/",
},
},
description: "这b班上得值不值 - 计算你的工作性价比 | Is This Job Worth It? - Calculate your job's value",
description: "这b班上得值不值 - 计算你的工作性价比 | Is My Job Worth It? - Calculate your job's value",
verification: {
google: "_OQGiIpYz87USAsgJV2C07-JJhQ8myV_4GoM1kDjFic",
},

View File

@@ -1,11 +1,14 @@
"use client";
import { useSearchParams } from 'next/navigation';
import ShareCard from '@/components/ShareCard';
import dynamic from 'next/dynamic';
import React, { Suspense } from 'react';
import { LanguageProvider } from '@/components/LanguageContext';
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
// 动态导入ShareCard组件禁用SSR
const ShareCard = dynamic(() => import('@/components/ShareCard'), { ssr: false });
// 一个包装组件负责从URL参数获取数据
function ShareCardWrapper() {
const searchParams = useSearchParams();
@@ -23,6 +26,7 @@ function ShareCardWrapper() {
const workDaysPerYear = searchParams.get('workDaysPerYear') || '250';
const countryCode = searchParams.get('countryCode') || 'CN';
const countryName = searchParams.get('countryName') || '中国';
const currencySymbol = searchParams.get('currencySymbol') || '¥';
// 额外参数 - 详细工作信息
const workDaysPerWeek = searchParams.get('workDaysPerWeek') || '5';
@@ -63,6 +67,7 @@ function ShareCardWrapper() {
workDaysPerYear={workDaysPerYear}
countryCode={countryCode}
countryName={countryName}
currencySymbol={currencySymbol}
// 详细工作信息
workDaysPerWeek={workDaysPerWeek}

View File

@@ -3,7 +3,7 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
// 定义语言类型
export type Language = 'zh' | 'en';
export type Language = 'zh' | 'en' | 'ja';
// 创建上下文接口
interface LanguageContextType {
@@ -22,24 +22,572 @@ const defaultContext: LanguageContextType = {
// 创建上下文
const LanguageContext = createContext<LanguageContextType>(defaultContext);
// 国家名称映射
export const countryNames: Record<Language, Record<string, string>> = {
zh: {
'AF': '阿富汗',
'AO': '安哥拉',
'AL': '阿尔巴尼亚',
'AR': '阿根廷',
'AM': '亚美尼亚',
'AG': '安提瓜和巴布达',
'AU': '澳大利亚',
'AT': '奥地利',
'AZ': '阿塞拜疆',
'BI': '布隆迪',
'BE': '比利时',
'BJ': '贝宁',
'BF': '布基纳法索',
'BD': '孟加拉国',
'BG': '保加利亚',
'BH': '巴林',
'BS': '巴哈马',
'BA': '波斯尼亚和黑塞哥维那',
'BY': '白俄罗斯',
'BZ': '伯利兹',
'BO': '玻利维亚',
'BR': '巴西',
'BB': '巴巴多斯',
'BN': '文莱达鲁萨兰国',
'BT': '不丹',
'BW': '博茨瓦纳',
'CF': '中非共和国',
'CA': '加拿大',
'CH': '瑞士',
'CL': '智利',
'CN': '中国',
'CI': '科特迪瓦',
'CM': '喀麦隆',
'CD': '刚果(金)',
'CG': '刚果(布)',
'CO': '哥伦比亚',
'KM': '科摩罗',
'CV': '佛得角',
'CR': '哥斯达黎加',
'CY': '塞浦路斯',
'CZ': '捷克共和国',
'DE': '德国',
'DJ': '吉布提',
'DM': '多米尼克',
'DK': '丹麦',
'DO': '多米尼加共和国',
'DZ': '阿尔及利亚',
'EC': '厄瓜多尔',
'EG': '阿拉伯埃及共和国',
'ES': '西班牙',
'EE': '爱沙尼亚',
'ET': '埃塞俄比亚',
'FI': '芬兰',
'FJ': '斐济',
'FR': '法国',
'GA': '加蓬',
'GB': '英国',
'GE': '格鲁吉亚',
'GH': '加纳',
'GN': '几内亚',
'GM': '冈比亚',
'GW': '几内亚比绍共和国',
'GQ': '赤道几内亚',
'GR': '希腊',
'GD': '格林纳达',
'GT': '危地马拉',
'GY': '圭亚那',
'HK': '香港特别行政区',
'HN': '洪都拉斯',
'HR': '克罗地亚',
'HT': '海地',
'HU': '匈牙利',
'ID': '印度尼西亚',
'IN': '印度',
'IE': '爱尔兰',
'IR': '伊朗伊斯兰共和国',
'IQ': '伊拉克',
'IS': '冰岛',
'IL': '以色列',
'IT': '意大利',
'JM': '牙买加',
'JO': '约旦',
'JP': '日本',
'KZ': '哈萨克斯坦',
'KE': '肯尼亚',
'KG': '吉尔吉斯斯坦',
'KH': '柬埔寨',
'KI': '基里巴斯',
'KN': '圣基茨和尼维斯',
'KR': '大韩民国',
'LA': '老挝',
'LB': '黎巴嫩',
'LR': '利比里亚',
'LY': '利比亚',
'LC': '圣卢西亚',
'LK': '斯里兰卡',
'LS': '莱索托',
'LT': '立陶宛',
'LU': '卢森堡',
'LV': '拉脱维亚',
'MO': '中国澳门特别行政区',
'MA': '摩洛哥',
'MD': '摩尔多瓦',
'MG': '马达加斯加',
'MV': '马尔代夫',
'MX': '墨西哥',
'MK': '北马其顿',
'ML': '马里',
'MT': '马耳他',
'MM': '缅甸',
'ME': '黑山',
'MN': '蒙古',
'MZ': '莫桑比克',
'MR': '毛里塔尼亚',
'MU': '毛里求斯',
'MW': '马拉维',
'MY': '马来西亚',
'NA': '纳米比亚',
'NE': '尼日尔',
'NG': '尼日利亚',
'NI': '尼加拉瓜',
'NL': '荷兰',
'NO': '挪威',
'NP': '尼泊尔',
'NZ': '新西兰',
'PK': '巴基斯坦',
'PA': '巴拿马',
'PE': '秘鲁',
'PH': '菲律宾',
'PG': '巴布亚新几内亚',
'PL': '波兰',
'PR': '波多黎各',
'PT': '葡萄牙',
'PY': '巴拉圭',
'PS': '约旦河西岸和加沙',
'QA': '卡塔尔',
'RO': '罗马尼亚',
'RU': '俄罗斯联邦',
'RW': '卢旺达',
'SA': '沙特阿拉伯',
'SD': '苏丹',
'SN': '塞内加尔',
'SG': '新加坡',
'SB': '所罗门群岛',
'SL': '塞拉利昂',
'SV': '萨尔瓦多',
'SO': '索马里',
'RS': '塞尔维亚',
'ST': '圣多美和普林西比',
'SR': '苏里南',
'SK': '斯洛伐克共和国',
'SI': '斯洛文尼亚',
'SE': '瑞典',
'SZ': '斯威士兰',
'SC': '塞舌尔',
'TC': '特克斯科斯群岛',
'TD': '乍得',
'TG': '多哥',
'TH': '泰国',
'TJ': '塔吉克斯坦',
'TL': '东帝汶',
'TT': '特立尼达和多巴哥',
'TN': '突尼斯',
'TR': '土耳其',
'TV': '图瓦卢',
'TW': '台湾',
'TZ': '坦桑尼亚',
'UG': '乌干达',
'UA': '乌克兰',
'UY': '乌拉圭',
'US': '美国',
'UZ': '乌兹别克斯坦',
'VC': '圣文森特和格林纳丁斯',
'VN': '越南',
'VU': '瓦努阿图',
'XK': '科索沃',
'ZA': '南非',
'ZM': '赞比亚',
'ZW': '津巴布韦'
},
en: {
'AF': 'Afghanistan',
'AO': 'Angola',
'AL': 'Albania',
'AR': 'Argentina',
'AM': 'Armenia',
'AG': 'Antigua and Barbuda',
'AU': 'Australia',
'AT': 'Austria',
'AZ': 'Azerbaijan',
'BI': 'Burundi',
'BE': 'Belgium',
'BJ': 'Benin',
'BF': 'Burkina Faso',
'BD': 'Bangladesh',
'BG': 'Bulgaria',
'BH': 'Bahrain',
'BS': 'Bahamas',
'BA': 'Bosnia and Herzegovina',
'BY': 'Belarus',
'BZ': 'Belize',
'BO': 'Bolivia',
'BR': 'Brazil',
'BB': 'Barbados',
'BN': 'Brunei Darussalam',
'BT': 'Bhutan',
'BW': 'Botswana',
'CF': 'Central African Republic',
'CA': 'Canada',
'CH': 'Switzerland',
'CL': 'Chile',
'CN': 'China',
'CI': 'Côte d\'Ivoire',
'CM': 'Cameroon',
'CD': 'Congo (DRC)',
'CG': 'Congo (Republic)',
'CO': 'Colombia',
'KM': 'Comoros',
'CV': 'Cape Verde',
'CR': 'Costa Rica',
'CY': 'Cyprus',
'CZ': 'Czech Republic',
'DE': 'Germany',
'DJ': 'Djibouti',
'DM': 'Dominica',
'DK': 'Denmark',
'DO': 'Dominican Republic',
'DZ': 'Algeria',
'EC': 'Ecuador',
'EG': 'Egypt',
'ES': 'Spain',
'EE': 'Estonia',
'ET': 'Ethiopia',
'FI': 'Finland',
'FJ': 'Fiji',
'FR': 'France',
'GA': 'Gabon',
'GB': 'United Kingdom',
'GE': 'Georgia',
'GH': 'Ghana',
'GN': 'Guinea',
'GM': 'Gambia',
'GW': 'Guinea-Bissau',
'GQ': 'Equatorial Guinea',
'GR': 'Greece',
'GD': 'Grenada',
'GT': 'Guatemala',
'GY': 'Guyana',
'HK': 'Hong Kong SAR',
'HN': 'Honduras',
'HR': 'Croatia',
'HT': 'Haiti',
'HU': 'Hungary',
'ID': 'Indonesia',
'IN': 'India',
'IE': 'Ireland',
'IR': 'Iran',
'IQ': 'Iraq',
'IS': 'Iceland',
'IL': 'Israel',
'IT': 'Italy',
'JM': 'Jamaica',
'JO': 'Jordan',
'JP': 'Japan',
'KZ': 'Kazakhstan',
'KE': 'Kenya',
'KG': 'Kyrgyzstan',
'KH': 'Cambodia',
'KI': 'Kiribati',
'KN': 'St. Kitts and Nevis',
'KR': 'South Korea',
'LA': 'Laos',
'LB': 'Lebanon',
'LR': 'Liberia',
'LY': 'Libya',
'LC': 'St. Lucia',
'LK': 'Sri Lanka',
'LS': 'Lesotho',
'LT': 'Lithuania',
'LU': 'Luxembourg',
'LV': 'Latvia',
'MO': 'Macao SAR',
'MA': 'Morocco',
'MD': 'Moldova',
'MG': 'Madagascar',
'MV': 'Maldives',
'MX': 'Mexico',
'MK': 'North Macedonia',
'ML': 'Mali',
'MT': 'Malta',
'MM': 'Myanmar',
'ME': 'Montenegro',
'MN': 'Mongolia',
'MZ': 'Mozambique',
'MR': 'Mauritania',
'MU': 'Mauritius',
'MW': 'Malawi',
'MY': 'Malaysia',
'NA': 'Namibia',
'NE': 'Niger',
'NG': 'Nigeria',
'NI': 'Nicaragua',
'NL': 'Netherlands',
'NO': 'Norway',
'NP': 'Nepal',
'NZ': 'New Zealand',
'PK': 'Pakistan',
'PA': 'Panama',
'PE': 'Peru',
'PH': 'Philippines',
'PG': 'Papua New Guinea',
'PL': 'Poland',
'PR': 'Puerto Rico',
'PT': 'Portugal',
'PY': 'Paraguay',
'PS': 'West Bank and Gaza',
'QA': 'Qatar',
'RO': 'Romania',
'RU': 'Russia',
'RW': 'Rwanda',
'SA': 'Saudi Arabia',
'SD': 'Sudan',
'SN': 'Senegal',
'SG': 'Singapore',
'SB': 'Solomon Islands',
'SL': 'Sierra Leone',
'SV': 'El Salvador',
'SO': 'Somalia',
'RS': 'Serbia',
'ST': 'São Tomé and Principe',
'SR': 'Suriname',
'SK': 'Slovak Republic',
'SI': 'Slovenia',
'SE': 'Sweden',
'SZ': 'Eswatini',
'SC': 'Seychelles',
'TC': 'Turks and Caicos Islands',
'TD': 'Chad',
'TG': 'Togo',
'TH': 'Thailand',
'TJ': 'Tajikistan',
'TL': 'Timor-Leste',
'TT': 'Trinidad and Tobago',
'TN': 'Tunisia',
'TR': 'Turkey',
'TV': 'Tuvalu',
'TW': 'Taiwan',
'TZ': 'Tanzania',
'UG': 'Uganda',
'UA': 'Ukraine',
'UY': 'Uruguay',
'US': 'United States',
'UZ': 'Uzbekistan',
'VC': 'St. Vincent and the Grenadines',
'VN': 'Vietnam',
'VU': 'Vanuatu',
'XK': 'Kosovo',
'ZA': 'South Africa',
'ZM': 'Zambia',
'ZW': 'Zimbabwe'
},
ja: {
'AF': 'アフガニスタン',
'AL': 'アルバニア',
'DZ': 'アルジェリア',
'AO': 'アンゴラ',
'AR': 'アルゼンチン',
'AM': 'アルメニア',
'AU': 'オーストラリア',
'AT': 'オーストリア',
'AZ': 'アゼルバイジャン',
'BI': 'ブルンジ',
'BE': 'ベルギー',
'BJ': 'ベナン',
'BF': 'ブルキナファソ',
'BD': 'バングラデシュ',
'BG': 'ブルガリア',
'BH': 'バーレーン',
'BS': 'バハマ',
'BA': 'ボスニア・ヘルツェゴビナ',
'BY': 'ベラルーシ',
'BZ': 'ベリーズ',
'BO': 'ボリビア',
'BR': 'ブラジル',
'BB': 'バルバドス',
'BN': 'ブルネイ',
'BT': 'ブータン',
'BW': 'ボツワナ',
'CF': '中央アフリカ共和国',
'CA': 'カナダ',
'CH': 'スイス',
'CL': 'チリ',
'CN': '中国',
'CI': 'コートジボワール',
'CM': 'カメルーン',
'CD': 'コンゴ民主共和国',
'CG': 'コンゴ共和国',
'CO': 'コロンビア',
'KM': 'コモロ',
'CV': 'カーボベルデ',
'CR': 'コスタリカ',
'CY': 'キプロス',
'CZ': 'チェコ共和国',
'DE': 'ドイツ',
'DJ': 'ジブチ',
'DM': 'ドミニカ国',
'DK': 'デンマーク',
'DO': 'ドミニカ共和国',
'EC': 'エクアドル',
'EG': 'エジプト',
'ES': 'スペイン',
'EE': 'エストニア',
'ET': 'エチオピア',
'FI': 'フィンランド',
'FJ': 'フィジー',
'FR': 'フランス',
'GA': 'ガボン',
'GB': 'イギリス',
'GE': 'ジョージア',
'GH': 'ガーナ',
'GN': 'ギニア',
'GM': 'ガンビア',
'GW': 'ギニアビサウ',
'GQ': '赤道ギニア',
'GR': 'ギリシャ',
'GD': 'グレナダ',
'GT': 'グアテマラ',
'GY': 'ガイアナ',
'HK': '香港特別行政区',
'HN': 'ホンジュラス',
'HR': 'クロアチア',
'HT': 'ハイチ',
'HU': 'ハンガリー',
'ID': 'インドネシア',
'IN': 'インド',
'IE': 'アイルランド',
'IR': 'イラン',
'IQ': 'イラク',
'IS': 'アイスランド',
'IL': 'イスラエル',
'IT': 'イタリア',
'JM': 'ジャマイカ',
'JO': 'ヨルダン',
'JP': '日本',
'KZ': 'カザフスタン',
'KE': 'ケニア',
'KG': 'キルギス',
'KH': 'カンボジア',
'KI': 'キリバス',
'KN': 'セントクリストファー・ネイビス',
'KR': '韓国',
'LA': 'ラオス',
'LB': 'レバノン',
'LR': 'リベリア',
'LY': 'リビア',
'LC': 'セントルシア',
'LK': 'スリランカ',
'LS': 'レソト',
'LT': 'リトアニア',
'LU': 'ルクセンブルク',
'LV': 'ラトビア',
'MO': 'マカオ特別行政区',
'MA': 'モロッコ',
'MD': 'モルドバ',
'MG': 'マダガスカル',
'MV': 'モルディブ',
'MX': 'メキシコ',
'MK': '北マケドニア',
'ML': 'マリ',
'MT': 'マルタ',
'MM': 'ミャンマー',
'ME': 'モンテネグロ',
'MN': 'モンゴル',
'MZ': 'モザンビーク',
'MR': 'モーリタニア',
'MU': 'モーリシャス',
'MW': 'マラウイ',
'MY': 'マレーシア',
'NA': 'ナミビア',
'NE': 'ニジェール',
'NG': 'ナイジェリア',
'NI': 'ニカラグア',
'NL': 'オランダ',
'NO': 'ノルウェー',
'NP': 'ネパール',
'NZ': 'ニュージーランド',
'PK': 'パキスタン',
'PA': 'パナマ',
'PE': 'ペルー',
'PH': 'フィリピン',
'PG': 'パプアニューギニア',
'PL': 'ポーランド',
'PR': 'プエルトリコ',
'PT': 'ポルトガル',
'PY': 'パラグアイ',
'PS': 'パレスチナ',
'QA': 'カタール',
'RO': 'ルーマニア',
'RU': 'ロシア',
'RW': 'ルワンダ',
'SA': 'サウジアラビア',
'SD': 'スーダン',
'SN': 'セネガル',
'SG': 'シンガポール',
'SB': 'ソロモン諸島',
'SL': 'シエラレオネ',
'SV': 'エルサルバドル',
'SO': 'ソマリア',
'RS': 'セルビア',
'ST': 'サントメ・プリンシペ',
'SR': 'スリナム',
'SK': 'スロバキア',
'SI': 'スロベジア',
'SE': 'スウェーデン',
'SZ': 'エスワティニ',
'SC': 'セーシェル',
'TC': 'タークス・カイコス諸島',
'TD': 'チャド',
'TG': 'トーゴ',
'TH': 'タイ',
'TJ': 'タジキスタン',
'TL': '東ティモール',
'TT': 'トリニダード・トバゴ',
'TN': 'チュニジア',
'TR': 'トルコ',
'TV': 'ツバル',
'TW': '台湾',
'TZ': 'タンザニア',
'UG': 'ウガンダ',
'UA': 'ウクライナ',
'UY': 'ウルグアイ',
'US': 'アメリカ合衆国',
'UZ': 'ウズベキスタン',
'VC': 'セントビンセント・グレナディーン',
'VN': 'ベトナム',
'VU': 'バヌアツ',
'XK': 'コソボ',
'ZA': '南アフリカ',
'ZM': 'ザンビア',
'ZW': 'ジンバブエ'
}
};
// 翻译数据
const translations: Record<Language, Record<string, string>> = {
zh: {
// 标题和导航
'title': '这b班上得值不值·测算版',
'version': 'v5.0.0',
'github': 'GitHub',
'email': 'Email',
'xiaohongshu': '小红书',
'redirect_notice': '已自动跳转,新网址无需科学上网',
'visits': '访问量',
'visitors': '访客数',
'star_request': '如果觉得好用请给项目点个⭐Star吧',
// 表单标签
'annual_salary_cny': '年薪总包(元)',
'annual_salary_foreign': '年薪总包(当地货币)',
'annual_salary': '年薪总包',
'salary_placeholder_cny': '税前年薪',
'salary_placeholder_foreign': '使用当地货币',
'salary_placeholder': '税前年薪',
'non_china_salary': '非中国地区薪资',
'ppp_factor': '购买力平价(PPP)转换因子',
'ppp_tooltip': 'PPP转换因子是将各国货币购买力标准化的指标。例如中国为4.19表示1美元在美国的购买力等同于4.19元人民币在中国的购买力。',
@@ -54,11 +602,11 @@ const translations: Record<Language, Record<string, string>> = {
'annual_leave': '年假天数/d',
'public_holidays': '法定假日/d',
'paid_sick_leave': '带薪病假/d',
'work_hours': '工时/h',
'work_hours': '工时/h',
'work_hours_tooltip': '工时:是指"下班时间-上班时间"的总时间,包括吃饭、午休、加班等(不含通勤)。',
'commute_hours': '通勤/h',
'commute_tooltip': '通勤时长是指上下班往返的总时间,即家到公司和公司回家的时间总和。',
'rest_time': '休息&摸鱼',
'rest_time': '休息&摸鱼/h',
// 环境系数
'job_stability': '合同类型',
@@ -182,9 +730,10 @@ const translations: Record<Language, Record<string, string>> = {
'share_working_days_per_year': '年工作天数',
'share_hometown_comment': '在家乡工作,让你既能追求事业,又能照顾家人,平衡感满满。家的温暖和熟悉的环境给你带来额外的安全感和幸福感。',
'share_not_hometown_comment': '要照顾好自己,按时吃饭休息,你一个人去得那么远。',
'share_tier1_city_comment': '虽然生活成本较高,但丰富的机会和广阔的平台能够助你更快成长。',
'share_tier2_city_comment': '生活节奏虽然没有一线城市那么快,但依然提供了不错的发展空间。这里的生活压力适中,让你能找到工作与生活之间的平衡。',
'share_tier3_city_comment': '你享受着低成本高质量的生活。虽然机会相对较少,但悠闲的生活节奏和较低的压力让你能更从容地面对人生。要照顾好自己,按时吃饭休息,你一个人去得那么远。',
'share_tier3_city_comment': '你享受着低成本高质量的生活。虽然机会相对较少,但悠闲的生活节奏和较低的压力让你能更从容地面对人生。',
'share_commute_short': '你的通勤时间很短,让你每天都能多出宝贵的时间用于自我提升或休息。',
'share_commute_medium': '你的通勤时间适中,不会让你感到太大压力,也可以利用这段时间听书或补觉。',
@@ -251,20 +800,22 @@ const translations: Record<Language, Record<string, string>> = {
},
en: {
// Title and navigation
'title': 'Is This Job Worth It?',
'version': 'v5.0.0',
'title': 'Is My Job Worth the Grind?',
'github': 'GitHub',
'email': 'Email',
'xiaohongshu': 'Rednote',
'redirect_notice': 'Redirected automatically to new domain',
'redirect_notice': 'Automatically redirected, no VPN needed',
'visits': 'Visits',
'visitors': 'Visitors',
'star_request': 'If you find this tool helpful, please give it a ⭐Star!',
// Form labels
'annual_salary_cny': 'Annual Salary (CNY)',
'annual_salary_foreign': 'Annual Salary (Local Currency)',
'annual_salary': 'Annual Salary',
'salary_placeholder_cny': 'Pre-tax annual salary',
'salary_placeholder_foreign': 'Use local currency',
'salary_placeholder': 'Pre-tax annual salary',
'non_china_salary': 'Non-China Salary',
'ppp_factor': 'Purchasing Power Parity Factor',
'ppp_tooltip': 'PPP factor standardizes purchasing power across countries. For example, China\'s 4.19 means that 1 USD in US has the same purchasing power as 4.19 CNY in China.',
@@ -363,7 +914,7 @@ const translations: Record<Language, Record<string, string>> = {
'share_back_to_calculator': 'Back to Calculator',
'share_your_job_worth_report': 'Your Job Worth Report',
'share_job_worth_report': 'Job Worth Report',
'share_custom_made': 'Custom made by "Is This Job Worth It?"',
'share_custom_made': 'Custom made by "Is My Job Worth It?"',
'share_generating': 'Generating...',
'share_download_report': 'Download Report',
'share_basic_info': 'Basic Information',
@@ -407,6 +958,7 @@ const translations: Record<Language, Record<string, string>> = {
'share_working_days_per_year': 'Working days/year',
'share_hometown_comment': 'Working in your hometown allows you to build your career while maintaining family connections - a valuable balance that contributes to overall life satisfaction.',
'share_not_hometown_comment': 'To take care of yourself, eat and rest on time, you\'re so far away by yourself.',
'share_tier1_city_comment': 'While living costs are high, the abundant opportunities and professional networks in major cities can accelerate your career growth.',
'share_tier2_city_comment': 'This regional center offers a good balance - decent career opportunities with more manageable living costs and work pressure compared to major cities.',
'share_tier3_city_comment': 'You enjoy a good quality of life with lower living costs. While career opportunities may be more limited, the relaxed pace and lower pressure are significant advantages.',
@@ -473,6 +1025,234 @@ const translations: Record<Language, Record<string, string>> = {
'rating_excellent': 'Excellent',
'rating_perfect': 'Outstanding',
'share_country': 'Work Country/Region',
},
ja: {
// タイトルとナビゲーション
'title': 'この仕事、割に合ってる?ブラック度診断',
'github': 'GitHub',
'email': 'Email',
'xiaohongshu': '小紅書',
'redirect_notice': '自動的にリダイレクトされました',
'visits': 'アクセス数',
'visitors': '訪問者数',
'star_request': '役に立ったら、⭐スターを付けてください!',
// フォームラベル
'annual_salary_cny': '年収(元)',
'annual_salary_foreign': '年収(現地通貨)',
'annual_salary': '年収',
'salary_placeholder_cny': '税引前の年収',
'salary_placeholder_foreign': '現地通貨で入力',
'salary_placeholder': '税引前の年収',
'non_china_salary': '中国以外の給与',
'ppp_factor': '購買力平価(PPP)換算係数',
'ppp_tooltip': 'PPP換算係数は各国の通貨の購買力を標準化する指標です。例えば、中国の4.19は、米国の1ドルが中国の4.19元と同等の購買力を持つことを意味します。',
'ppp_placeholder': 'PPP換算係数を入力',
'ppp_common_regions': '主な地域:中国:4.19、日本:102.59、米国:1.00、シンガポール:0.84',
'view_more': 'もっと見る',
'country_selection': '勤務国・地域',
'selected_ppp': '現在のPPP値',
'work_days_per_week': '週の勤務日数/日',
'wfh_days_per_week': 'リモートワーク日数/週',
'wfh_tooltip': 'リモートワークとは在宅勤務のことです。週の勤務日のうち、何日自宅で仕事をするかを入力してください。',
'annual_leave': '年次有給休暇/日',
'public_holidays': '祝日/日',
'paid_sick_leave': '有給病気休暇/日',
'work_hours': '勤務時間/時間',
'work_hours_tooltip': '勤務時間:始業から終業までの総時間(昼食、休憩、残業を含む、通勤時間は除く)',
'commute_hours': '通勤時間/時間',
'commute_tooltip': '通勤時間は自宅と職場の往復にかかる総時間です。',
'rest_time': '休憩&サボり時間',
// 環境係数
'job_stability': '雇用形態',
'job_private': '一般企業(更新あり)',
'job_foreign': '外資系企業(更新あり)',
'job_state': '大企業・終身雇用',
'job_government': '公務員・準公務員',
'work_environment': '職場環境',
'env_remote': '僻地の工場・現場・屋外',
'env_factory': '工場・現場・屋外',
'env_normal': '一般的なオフィス',
'env_cbd': '都心の高級オフィス',
'city_factor': '勤務地(生活コストによる)',
'city_tier1': '東京23区・大阪市中心部',
'city_newtier1': '横浜・名古屋・福岡市中心部',
'city_tier2': '県庁所在地・中核市',
'city_tier3': '地方都市',
'city_tier4': '小都市',
'city_county': '郡部',
'city_town': '町村部',
'hometown': '地元で働いていますか',
'not_hometown': '地元ではない',
'is_hometown': '地元である',
'leadership': '上司・経営者',
'leader_bad': '嫌われている',
'leader_strict': '厳しい管理',
'leader_normal': '普通の関係',
'leader_good': '理解がある',
'leader_favorite': '気に入られている',
'teamwork': '職場の人間関係',
'team_bad': '最悪な環境',
'team_normal': '事務的な関係',
'team_good': '協力的な環境',
'team_excellent': '仲の良い職場',
'shuttle': '送迎バス(加点項目)',
'shuttle_none': 'なし',
'shuttle_inconvenient': '不便なバス',
'shuttle_convenient': '便利なバス',
'shuttle_direct': '直行便あり',
'canteen': '社員食堂(加点項目)',
'canteen_none': 'なし/まずい',
'canteen_average': '普通',
'canteen_good': '美味しい',
'canteen_excellent': '非常に美味しい',
// 教育と経験
'education_level': '学歴',
'degree_type': '学位タイプ',
'below_bachelor': '短大・専門学校以下',
'bachelor': '学士(大学卒)',
'masters': '修士',
'phd': '博士',
'school_type': '大学タイプ',
'school_second_tier': '一般大学',
'school_first_tier_bachelor': 'MARCH・関関同立・QS200位',
'school_elite_bachelor': '東大・京大・早慶・QS50位',
'school_first_tier_higher': '国公立大学院・QS100位',
'school_elite_higher': '東大・京大大学院・QS30位',
'bachelor_background': '学部背景',
'work_years': '勤務年数',
'fresh_graduate': '新卒',
'years_1_3': '1-3年',
'years_3_5': '3-5年',
'years_5_8': '5-8年',
'years_8_10': '8-10年',
'years_10_12': '10-12年',
'years_above_12': '12年以上',
// 結果
'working_days_per_year': '年間勤務日数',
'days_unit': '日',
'average_daily_salary': '1日あたりの給与',
'job_value': '仕事の価値評価',
'view_report': '私の仕事診断レポートを見る',
// ShareCardコンポーネント
'share_back_to_calculator': '計算機に戻る',
'share_your_job_worth_report': 'あなたの仕事価値レポート',
'share_job_worth_report': '仕事価値レポート',
'share_custom_made': '「この仕事、割に合ってる?ブラック度診断」による分析',
'share_generating': '生成中...',
'share_download_report': 'レポートをダウンロード',
'share_basic_info': '基本情報',
'share_work_city': '勤務地',
'share_is_hometown': '地元',
'share_yes': 'はい',
'share_no': 'いいえ',
'share_daily_salary': '日給',
'share_day': '日',
'share_days': '日',
'share_work_hours_title': '勤務時間',
'share_hours': '時間',
'share_daily_work_hours': '1日の勤務時間',
'share_daily_commute_hours': '1日の通勤時間',
'share_rest_time': '休憩時間',
'share_weekly_work_days': '週の勤務日数',
'share_remote_work': 'リモートワーク',
'share_days_per_week': '日/週',
'share_shuttle_service': '送迎バス',
'share_annual_leave': '年次有給休暇',
'share_paid_sick_leave': '有給病気休暇',
'share_days_per_year': '日/年',
'share_work_environment_title': '職場環境',
'share_office_environment': 'オフィス環境',
'share_leadership_relation': '上司との関係',
'share_colleague_relationship': '同僚との関係',
'share_canteen_quality': '社員食堂',
'share_education_and_experience': '学歴と経験',
'share_highest_degree': '最終学歴',
'share_school_type_label': '大学タイプ',
'share_work_years_label': '勤務年数',
'share_contract_type_label': '雇用形態',
'share_final_assessment': '総合評価',
'share_low_value_assessment_1': 'この仕事はあなたにとって日々が苦痛で、まさにブラック企業の特徴を持っています。',
'share_low_value_assessment_2': 'この仕事は非常に厳しいですが、より良い将来へのステップになるかもしれません。',
'share_medium_value_assessment_1': 'この仕事は普通で、特に素晴らしいわけでも悪いわけでもありません。',
'share_medium_value_assessment_2': 'この仕事はそこそこ満足感を与えてくれる、悪くない選択です。',
'share_high_value_assessment_1': 'この仕事はあなたの期待のほとんどを満たし、やりがいを感じられます。',
'share_high_value_assessment_2': 'この仕事はあなたにぴったりで、チャレンジと報酬のバランスが取れています。',
'share_high_value_assessment_3': '理想的な仕事を見つけましたね!このような機会は滅多にありません!',
'share_working_days_per_year': '年間勤務日数',
'share_hometown_comment': 'あなたの故郷で働くことで、キャリアを構築しながら家族とのつながりを維持することができます - これは総合的な生活満足度に貢献する貴重なバランスです。',
'share_not_hometown_comment': '自分の面倒をしっかり見て、定時に食事と休息を取るように。あなたは一人でとても遠くまで来ました。',
'share_tier1_city_comment': '生活費は高いですが、大都市における豊富な機会と専門的なネットワークはあなたのキャリア成長を加速させることができます。',
'share_tier2_city_comment': '県庁所在地クラスの都市では、適度な機会と比較的落ち着いた生活環境のバランスが取れています。',
'share_tier3_city_comment': '地方都市では生活コストが低く、ゆとりある生活が送れます。キャリア機会は限られるかもしれませんが、ストレスの少ない環境は大きな魅力です。',
'share_commute_short': '通勤時間が短いため、自己啓発やリラックスのための貴重な時間が確保できています。',
'share_commute_medium': '適度な通勤時間は負担にならず、オーディオブックや音楽を楽しむ時間として活用できます。',
'share_commute_long': '長時間の通勤は貴重な時間を消費し、心身の健康に影響を与える可能性があります。可能であれば引越しや在宅勤務の検討をおすすめします。',
'share_wfh_high': 'リモートワークの機会が多いため、通勤の負担が大幅に軽減され、生活の質が向上しています。',
'share_wfh_medium': '部分的なリモートワークにより、通勤時間を節約できています。',
'share_shuttle_service_good': '会社の送迎バスは価値ある福利厚生で、通勤をより快適にしています。',
'share_cbd_environment': '都心のオフィス環境は専門的かつ近代的で、ビジネスサービスやネットワーキングの機会へのアクセスが容易です。',
'share_factory_environment': '工場や屋外での勤務は独自の課題がありますが、耐久力と実践的な問題解決能力も育てています。',
'share_normal_environment': '職場環境は基本的な設備が整っており、生産的に仕事ができる条件が揃っています。',
'share_leadership_excellent': "上司の信頼を得ていることで、多くのチャンスと影響力を持つことができますが、同時に高い期待に応える責任も伴います。",
'share_leadership_good': '理解のある上司はあなたの貢献を認め、成功に必要な指導を提供してくれます - これは職場での貴重な財産です。',
'share_leadership_normal': '上司との関係は明快で機能的であり、余計な複雑さなく明確な期待が示されています。',
'share_leadership_strict': '厳格な管理下で働くことは挑戦的ですが、職業人としての規律と細部への注意力を養うことができます。',
'share_leadership_bad': '上司との緊張関係は課題をもたらし、慎重なコミュニケーションと人間関係よりも成果物に焦点を当てる必要があります。',
'share_teamwork_excellent': '同僚との強い個人的なつながりは、仕事の満足度と効率性を高める支援ネットワークを作り出しています。',
'share_teamwork_good': '協力的なチーム環境は相互サポートと効果的なコミュニケーションを促進し、日々の仕事をより快適で生産的にします。',
'share_teamwork_normal': '同僚と専門的でありながら過度に個人的ではない関係を維持することで、十分なサポートを受けながら仕事に集中できます。',
'share_teamwork_bad': '困難なチームダイナミクスには適応力と自立性が求められ、これが貴重な独立性と回復力を育てることがあります。',
'share_workhours_balanced': 'バランスの取れた勤務スケジュールは、個人生活のための十分な時間を確保し、長期的な持続可能なパフォーマンスに貢献します。',
'share_workhours_long': '長時間の勤務は管理可能ですが、エネルギーを維持し燃え尽き症候群を防ぐための注意が必要です。',
'share_workhours_excessive': '持続不可能なほど長い勤務時間は、長期的に健康とパフォーマンスに影響を与える可能性があります。業務量の調整について話し合うことを検討してください。',
'share_rest_adequate': '十分な休憩時間は、一日を通してエネルギーレベルと生産性を維持するのに役立ちます。',
'share_rest_insufficient': '限られた休憩時間は、健康と集中力を維持するために短い運動休憩を取り入れる必要性を示唆しています。',
'share_leave_abundant': '充実した休暇制度は、リフレッシュと個人的な追求のための十分な時間を提供し、持続的なモチベーションに不可欠です。',
'share_leave_limited': '限られた休暇時間では、その回復効果を最大化するために休日の戦略的な計画が重要になります。',
'share_phd_comment': '博士号の資格は専門的なポジションへの道を開き、高度な研究と分析能力を証明します。',
'share_masters_comment': "修士号は高度な知識と献身を示し、今日の競争の激しい就職市場で価値ある資格です。",
'share_bachelor_comment': "学士号は堅実な基盤を提供し、実践的な経験と組み合わせることで、多様なキャリア機会を可能にします。",
'share_below_bachelor_comment': "学士未満の正式な教育は一部の分野で課題をもたらす可能性がありますが、実践的なスキルと経験は同様に価値ある資産となります。",
'share_fresh_graduate_comment': '新卒者として、あなたの新鮮な視点と最新の知識は資産であり、成長と学習のための無限の可能性とバランスを取ることができます。',
'share_experienced_comment': '豊富な職務経験は、あなたの効果と自信を高める貴重な文脈と判断力を提供します。',
'share_mid_career_comment': '数年の経験を持つあなたは、業界と個人の強みの両方を理解し、戦略的なキャリア開発の準備ができています。',
'share_government_job_comment': '公共部門の雇用の安定性はキャリアの不確実性を軽減し、より自信を持って長期的な計画を立てることができます。',
'share_private_job_comment': '民間部門の雇用にはある程度の不確実性がありますが、多くの場合、加速された成長と報酬の機会を提供します。',
'share_salary_high_cny': '日給が競争力があり、必需品と自由裁量の支出の両方に対して財政的安定性と柔軟性を提供します。',
'share_salary_medium_cny': '給与は基本的なニーズを快適に満たしていますが、最適な財政状態のために思慮深い予算計画が必要です。',
'share_salary_low_cny': '現在の給与水準では、収入増加の機会を探りながら、慎重な財務管理が必要です。',
'share_salary_high_foreign': '日給が競争力があり、必需品と自由裁量の支出の両方に対して財政的安定性と柔軟性を提供します。',
'share_salary_medium_foreign': '給与は基本的なニーズを快適に満たしていますが、最適な財政状態のために思慮深い予算計画が必要です。',
'share_salary_low_foreign': '現在の給与水準では、収入増加の機会を探りながら、慎重な財務管理が必要です。',
'share_high_cost_city': '生活費の高い地域では、慎重な財務計画が報酬の価値を最大化するのに役立ちます。',
'share_low_cost_city': '生活費の低い地域では、給与がより高い購買力と貯蓄の可能性をもたらします。',
'share_value_low': '現在の仕事の価値は限られているかもしれませんが、将来の成長に必要な経験を提供するかもしれません。次のキャリアステップの準備をしながら、すべての側面から学びを得ることが大切です。',
'share_value_medium': 'あなたの仕事は長所と改善すべき点の両方があるバランスのとれた価値を提供しています。ポジティブな側面を活かしながら、課題に対処するための戦略を立てましょう。',
'share_value_high': '維持・発展させる価値のある高価値の職場を見つけました。あなたの強みを引き続き伸ばし、この役割が提供する満足感を大切にしましょう。',
'share_summary_advice': '総合的なアドバイス',
// 評価
'rating_enter_salary': '給与を入力してください',
'rating_terrible': '最悪',
'rating_poor': '悪い',
'rating_average': '普通',
'rating_good': '良い',
'rating_great': '素晴らしい',
'rating_excellent': '非常に優れている',
'rating_perfect': '理想的',
'share_country': '勤務国・地域',
}
};
@@ -484,7 +1264,7 @@ export const LanguageProvider: React.FC<{children: ReactNode}> = ({ children })
// 首次渲染时检查本地存储的语言设置
useEffect(() => {
const savedLanguage = localStorage.getItem('language') as Language;
if (savedLanguage && (savedLanguage === 'zh' || savedLanguage === 'en')) {
if (savedLanguage && (savedLanguage === 'zh' || savedLanguage === 'en' || savedLanguage === 'ja')) {
setLanguageState(savedLanguage);
}
}, []);

View File

@@ -26,7 +26,17 @@ export const LanguageSwitcher: React.FC = () => {
: 'bg-gray-100 text-gray-800 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600'
}`}
>
En
English
</button>
<button
onClick={() => setLanguage('ja')}
className={`px-1.5 py-0.5 text-xs font-medium rounded-md transition-colors ${
language === 'ja'
? 'bg-blue-500 text-white dark:bg-blue-600'
: 'bg-gray-100 text-gray-800 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600'
}`}
>
</button>
</div>
);

View File

@@ -2,7 +2,6 @@
import React, { useRef, useState, useEffect } from 'react';
import { ArrowLeft, Download } from 'lucide-react';
import html2canvas from 'html2canvas';
import Link from 'next/link';
import { useLanguage } from './LanguageContext';
@@ -21,6 +20,7 @@ interface ShareCardProps {
workDaysPerYear: string;
countryCode: string;
countryName: string;
currencySymbol: string;
// 详细工作信息
workDaysPerWeek: string;
@@ -48,6 +48,12 @@ interface ShareCardProps {
// 将中文评级转换为翻译键
const getAssessmentKey = (assessment: string): string => {
// 如果已经是翻译键,直接返回
if (assessment.startsWith('rating_')) {
return assessment;
}
// 否则,将中文评级转换为翻译键
switch (assessment) {
case '惨绝人寰': return 'rating_terrible';
case '略惨': return 'rating_poor';
@@ -195,9 +201,20 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
const [fadeIn, setFadeIn] = useState(false);
const { t, language } = useLanguage();
// 客户端渲染标志
const [isClient, setIsClient] = useState(false);
// 确保只在客户端执行
useEffect(() => {
setIsClient(true);
}, []);
// 页面载入动画效果
useEffect(() => {
setFadeIn(true);
// 确保只在客户端执行
if (typeof window !== 'undefined') {
setFadeIn(true);
}
}, []);
// 生成个性化评价
@@ -235,17 +252,23 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
const cityName = getCityName(props.cityFactor, t);
const isHomeTown = props.homeTown === 'yes';
let cityComment = "";
if (isHomeTown) {
cityComment = t('share_hometown_comment');
// 先根据城市等级添加评价
if (props.cityFactor === '0.70' || props.cityFactor === '0.80') {
cityComment = t('share_tier1_city_comment');
} else if (props.cityFactor === '1.0' || props.cityFactor === '1.10') {
cityComment = t('share_tier2_city_comment');
} else {
if (props.cityFactor === '0.70' || props.cityFactor === '0.80') {
cityComment = t('share_tier1_city_comment');
} else if (props.cityFactor === '1.0' || props.cityFactor === '1.10') {
cityComment = t('share_tier2_city_comment');
} else {
cityComment = t('share_tier3_city_comment');
}
cityComment = t('share_tier3_city_comment');
}
// 然后添加家乡相关评价
if (isHomeTown) {
cityComment += " " + t('share_hometown_comment');
} else {
cityComment += " " + t('share_not_hometown_comment');
}
comments.push({
title: t('share_work_city'),
content: cityComment,
@@ -466,7 +489,7 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
content: salaryComment,
emoji: "💰",
details: [
{ label: t('share_daily_salary'), value: `${isYuan ? '¥' : '$'}${dailySalary}/${t('share_day')}` },
{ label: t('share_daily_salary'), value: `${props.currencySymbol}${dailySalary}/${t('share_day')}` },
{ label: t('share_working_days_per_year'), value: `${props.workDaysPerYear} ${t('share_days')}` }
]
});
@@ -491,6 +514,21 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
return comments;
})();
// 是否是移动设备(响应式设计辅助函数)
const [isMobile, setIsMobile] = useState(false);
// 检测设备类型
useEffect(() => {
if (typeof window !== 'undefined') {
const checkMobile = () => {
setIsMobile(window.innerWidth < 640);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}
}, []);
// 处理下载图片 - 使用简化版报告
const handleDownload = async () => {
if (!simpleReportRef.current || isDownloading) return;
@@ -501,6 +539,10 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
// 获取简化版报告元素
const element = simpleReportRef.current;
// 动态导入html2canvas确保只在客户端加载
const html2canvasModule = await import('html2canvas');
const html2canvas = html2canvasModule.default;
// 使用html2canvas生成图片
const canvas = await html2canvas(element, {
scale: 2,
@@ -540,47 +582,54 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
return (
<div className={`min-h-screen bg-gradient-to-br ${getBackground()} flex flex-col items-center justify-start p-4 md:p-8 transition-opacity duration-1000 ${fadeIn ? 'opacity-100' : 'opacity-0'} dark:text-white`}>
{/* 返回按钮 */}
<div className="w-full max-w-4xl mb-6">
<Link href="/" className="flex items-center gap-2 text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors">
<ArrowLeft className="w-4 h-4" />
<div className="w-full max-w-4xl mb-4 md:mb-6">
<Link href="/" className="flex items-center gap-1.5 text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors">
<ArrowLeft className="w-3.5 h-3.5" />
<span>{t('share_back_to_calculator')}</span>
</Link>
</div>
<div ref={reportRef} className="w-full max-w-4xl bg-white rounded-xl shadow-xl p-6 md:p-10">
{/* 标题 */}
<div className="mb-10 text-center">
<div className="text-6xl mb-4">{getEmoji(parseFloat(props.value))}</div>
<h1 className="text-3xl md:text-4xl font-bold mb-3 bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-purple-600">
<div ref={reportRef} className="w-full max-w-4xl bg-white rounded-xl shadow-xl p-4 md:p-10">
{/* 标题 - 移动端更紧凑 */}
<div className="mb-5 md:mb-10 text-center">
<div className="text-4xl md:text-6xl mb-2 md:mb-4">{isClient ? getEmoji(parseFloat(props.value)) : '😊'}</div>
<h1 className="text-xl md:text-3xl font-bold mb-2 md:mb-3 bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-purple-600">
{t('share_your_job_worth_report')}
</h1>
<div className="flex justify-center items-center gap-3">
<span className="text-2xl font-bold px-3 py-1 rounded-lg" style={{ color: getColorFromClassName(props.assessmentColor), backgroundColor: `${getColorFromClassName(props.assessmentColor)}20` }}>
<div className="flex justify-center items-center gap-2">
<span className="text-lg md:text-2xl font-bold px-2 py-0.5 rounded-lg" style={{ color: getColorFromClassName(props.assessmentColor), backgroundColor: `${getColorFromClassName(props.assessmentColor)}20` }}>
{props.value}
</span>
<span className="text-lg text-gray-700">{t(getAssessmentKey(props.assessment))}</span>
<span className="text-base md:text-lg text-gray-700">{isClient ? t(getAssessmentKey(props.assessment)) : ''}</span>
</div>
</div>
{/* 性价比评语卡片 */}
<div className="space-y-8">
{personalizedComments.map((comment, index) => (
<div key={index} className="bg-gradient-to-r from-gray-50 to-gray-100 rounded-xl p-6 shadow-md transition-all hover:shadow-lg">
<div className="flex items-start gap-4">
<div className="text-4xl flex-shrink-0">{comment.emoji}</div>
{/* 性价比评语卡片 - 移动端更紧凑 */}
<div className="space-y-4 md:space-y-6">
{isClient && personalizedComments.map((comment, index) => (
<div key={index} className="bg-gradient-to-r from-gray-50 to-gray-100 rounded-lg md:rounded-xl p-3 md:p-5 shadow-sm transition-all hover:shadow-md">
<div className="flex items-start gap-2.5 md:gap-4">
<div className="text-2xl md:text-4xl flex-shrink-0 mt-0.5">{comment.emoji}</div>
<div className="flex-1">
<h3 className="text-xl font-bold mb-2 text-gray-800">{comment.title}</h3>
<p className="text-gray-700 leading-relaxed mb-4">{comment.content}</p>
<h3 className="text-base md:text-lg font-bold mb-1 md:mb-2 text-gray-800">{comment.title}</h3>
<p className="text-xs md:text-sm text-gray-700 leading-relaxed mb-2 md:mb-3">{comment.content}</p>
{/* 用户选项详情 */}
{/* 用户选项详情 - 移动端使用行内排列 */}
{comment.details && comment.details.length > 0 && (
<div className="mt-3 pt-3 border-t border-gray-200">
<div className="grid grid-cols-2 gap-2">
<div className="mt-2 pt-2 border-t border-gray-200">
<div className={isMobile ? "flex flex-wrap gap-x-4 gap-y-1.5" : "grid grid-cols-2 gap-2"}>
{comment.details.map((detail, i) => (
<div key={i} className="flex flex-col">
<span className="text-xs text-gray-500">{detail.label}</span>
<span className="text-sm font-medium text-gray-800">{detail.value}</span>
</div>
isMobile ? (
<div key={i} className="flex items-center text-xs">
<span className="text-gray-500 mr-1">{detail.label}:</span>
<span className="font-medium text-gray-800">{detail.value}</span>
</div>
) : (
<div key={i} className="flex justify-between items-center">
<span className="text-xs text-gray-500">{detail.label}</span>
<span className="text-xs md:text-sm font-medium text-gray-800">{detail.value}</span>
</div>
)
))}
</div>
</div>
@@ -591,192 +640,201 @@ const ShareCard: React.FC<ShareCardProps> = (props) => {
))}
</div>
{/* 底部信息 */}
<div className="mt-10 text-center text-gray-500 space-y-1">
{/* 底部信息 - 更小的文字 */}
<div className="mt-6 md:mt-10 text-center text-gray-500 space-y-0.5 text-xs md:text-sm">
<div>{t('share_custom_made')}</div>
<div>worthjob.zippland.com</div>
</div>
</div>
{/* 操作按钮 */}
<div className="flex justify-center gap-4 mt-8">
{/* 操作按钮 - 更小的按钮 */}
<div className="flex justify-center gap-4 mt-4 md:mt-8">
<button
onClick={handleDownload}
disabled={isDownloading}
className="flex items-center gap-2 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg shadow-md transition-colors"
className="flex items-center gap-1.5 px-4 py-2 md:px-6 md:py-3 bg-blue-600 hover:bg-blue-700 text-white text-sm md:text-base rounded-lg shadow-md transition-colors"
>
<Download className="w-5 h-5" />
<Download className="w-4 h-4 md:w-5 md:h-5" />
{isDownloading ? t('share_generating') : t('share_download_report')}
</button>
</div>
{/* 简化版报告,仅用于下载,在页面中隐藏 */}
<div className="fixed top-0 left-0 opacity-0 pointer-events-none">
<div ref={simpleReportRef} className="w-[800px] bg-white p-8 text-gray-900" style={{ fontFamily: 'system-ui, -apple-system, sans-serif' }}>
<div className="border border-gray-200 rounded-lg overflow-hidden shadow-sm">
{/* 报告头部 - 渐变背景 */}
<div className="bg-gradient-to-r from-blue-50 to-purple-50 p-8 border-b border-gray-200">
<div className="text-center">
<div className="text-5xl mb-4">{getEmoji(parseFloat(props.value))}</div>
<h1 className="text-2xl font-bold text-gray-800 mb-2">{t('share_job_worth_report')}</h1>
<div className="inline-block px-4 py-2 rounded-full bg-white shadow-sm">
<span className="font-semibold text-xl" style={{ color: getColorFromClassName(props.assessmentColor) }}>
{props.value} - {t(getAssessmentKey(props.assessment))}
</span>
{isClient && (
<div className="fixed top-0 left-0 opacity-0 pointer-events-none">
<div ref={simpleReportRef} className="w-[800px] bg-white p-8 text-gray-900" style={{ fontFamily: 'system-ui, -apple-system, sans-serif' }}>
<div className="border border-gray-200 rounded-lg overflow-hidden shadow-sm">
{/* 报告头部 - 渐变背景 */}
<div className="bg-gradient-to-r from-blue-50 to-purple-50 p-8 border-b border-gray-200">
<div className="text-center">
<div className="text-5xl mb-4">{getEmoji(parseFloat(props.value))}</div>
<h1 className="text-2xl font-bold text-gray-800 mb-2">{t('share_job_worth_report')}</h1>
<div className="inline-block px-4 py-2 rounded-full bg-white shadow-sm">
<span className="font-semibold text-xl" style={{ color: getColorFromClassName(props.assessmentColor) }}>
{props.value} - {t(getAssessmentKey(props.assessment))}
</span>
</div>
</div>
</div>
</div>
{/* 报告内容 */}
<div className="p-6">
{/* 数据表格 */}
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
{/* 基础信息 */}
<div className="col-span-2 mb-4">
<h2 className="font-bold text-gray-800 text-lg pb-2 mb-3 border-b border-gray-200 flex items-center">
<span className="mr-2">📊</span> {t('share_basic_info')}
</h2>
<div className="grid grid-cols-2 gap-4">
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_work_city')}</div>
<div className="font-medium text-gray-800 mt-1">{getCityName(props.cityFactor, t)}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_country')}</div>
<div className="font-medium text-gray-800 mt-1">{props.countryName}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_is_hometown')}</div>
<div className="font-medium text-gray-800 mt-1">{props.homeTown === 'yes' ? t('share_yes') : t('share_no')}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_daily_salary')}</div>
<div className="font-medium text-gray-800 mt-1">{props.isYuan === 'true' ? '¥' : '$'}{props.dailySalary}/{t('share_day')}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_working_days_per_year')}</div>
<div className="font-medium text-gray-800 mt-1">{props.workDaysPerYear} {t('share_days')}</div>
</div>
</div>
</div>
{/* 工作时间 */}
<div className="col-span-1">
<h2 className="font-bold text-gray-800 text-lg pb-2 mb-3 border-b border-gray-200 flex items-center">
<span className="mr-2"></span> {t('share_work_hours_title')}
</h2>
<div className="space-y-3">
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_daily_work_hours')}</span>
<span className="font-medium text-gray-800">{props.workHours} {t('share_hours')}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_daily_commute_hours')}</span>
<span className="font-medium text-gray-800">{props.commuteHours} {t('share_hours')}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_rest_time')}</span>
<span className="font-medium text-gray-800">{props.restTime} {t('share_hours')}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_weekly_work_days')}</span>
<span className="font-medium text-gray-800">{props.workDaysPerWeek} {t('share_days')}</span>
</div>
</div>
</div>
{/* 工作环境 */}
<div className="col-span-1">
<h2 className="font-bold text-gray-800 text-lg pb-2 mb-3 border-b border-gray-200 flex items-center">
<span className="mr-2">🏢</span> {t('share_work_environment_title')}
</h2>
<div className="space-y-3">
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_office_environment')}</span>
<span className="font-medium text-gray-800">{getWorkEnvironmentDesc(props.workEnvironment, t)}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_leadership_relation')}</span>
<span className="font-medium text-gray-800">{getLeadershipDesc(props.leadership, t)}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_colleague_relationship')}</span>
<span className="font-medium text-gray-800">{getTeamworkDesc(props.teamwork, t)}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_canteen_quality')}</span>
<span className="font-medium text-gray-800">{getCanteenDesc(props.canteen, t)}</span>
</div>
</div>
</div>
{/* 教育背景 */}
<div className="col-span-2 mt-2">
<h2 className="font-bold text-gray-800 text-lg pb-2 mb-3 border-b border-gray-200 flex items-center">
<span className="mr-2">📚</span> {t('share_education_and_experience')}
</h2>
<div className="grid grid-cols-2 gap-4">
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_highest_degree')}</div>
<div className="font-medium text-gray-800 mt-1">{getDegreeDesc(props.degreeType, t)}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_school_type_label')}</div>
<div className="font-medium text-gray-800 mt-1">{getSchoolTypeDesc(props.schoolType, props.degreeType, t)}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_work_years_label')}</div>
<div className="font-medium text-gray-800 mt-1">{getWorkYearsDesc(props.workYears, t)}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_contract_type_label')}</div>
<div className="font-medium text-gray-800 mt-1">{getJobStabilityDesc(props.jobStability, t)}</div>
</div>
</div>
</div>
{/* 结论 */}
<div className="col-span-2 mt-4">
<div className="rounded-lg bg-gradient-to-r from-blue-50 to-purple-50 p-6 border border-gray-200">
<h2 className="font-bold text-gray-800 text-lg mb-3 flex items-center">
<span className="mr-2">💎</span> {t('share_final_assessment')}
{/* 报告内容 */}
<div className="p-6">
{/* 数据表格 */}
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
{/* 基础信息 */}
<div className="col-span-2 mb-4">
<h2 className="font-bold text-gray-800 text-lg pb-2 mb-3 border-b border-gray-200 flex items-center">
<span className="mr-2">📊</span> {t('share_basic_info')}
</h2>
<div className="flex items-center mb-3">
<div className="text-4xl mr-3">{getEmoji(parseFloat(props.value))}</div>
<div className="text-xl font-bold" style={{ color: getColorFromClassName(props.assessmentColor) }}>
{props.value} - {t(getAssessmentKey(props.assessment))}
<div className="grid grid-cols-2 gap-4">
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_work_city')}</div>
<div className="font-medium text-gray-800 mt-1">{getCityName(props.cityFactor, t)}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_country')}</div>
<div className="font-medium text-gray-800 mt-1">{props.countryName}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_is_hometown')}</div>
<div className="font-medium text-gray-800 mt-1">{props.homeTown === 'yes' ? t('share_yes') : t('share_no')}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_daily_salary')}</div>
<div className="font-medium text-gray-800 mt-1">{props.currencySymbol}{props.dailySalary}/{t('share_day')}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_working_days_per_year')}</div>
<div className="font-medium text-gray-800 mt-1">{props.workDaysPerYear} {t('share_days')}</div>
</div>
</div>
<p className="text-gray-700">
{parseFloat(props.value) < 1.0
? t('share_value_low')
: parseFloat(props.value) <= 2.0
? t('share_value_medium')
: t('share_value_high')
}
</p>
</div>
{/* 工作时间 */}
<div className="col-span-1">
<h2 className="font-bold text-gray-800 text-lg pb-2 mb-3 border-b border-gray-200 flex items-center">
<span className="mr-2"></span> {t('share_work_hours_title')}
</h2>
<div className="space-y-3">
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_daily_work_hours')}</span>
<span className="font-medium text-gray-800">{props.workHours} {t('share_hours')}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_daily_commute_hours')}</span>
<span className="font-medium text-gray-800">{props.commuteHours} {t('share_hours')}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_rest_time')}</span>
<span className="font-medium text-gray-800">{props.restTime} {t('share_hours')}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_weekly_work_days')}</span>
<span className="font-medium text-gray-800">{props.workDaysPerWeek} {t('share_days')}</span>
</div>
</div>
</div>
{/* 工作环境 */}
<div className="col-span-1">
<h2 className="font-bold text-gray-800 text-lg pb-2 mb-3 border-b border-gray-200 flex items-center">
<span className="mr-2">🏢</span> {t('share_work_environment_title')}
</h2>
<div className="space-y-3">
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_office_environment')}</span>
<span className="font-medium text-gray-800">{getWorkEnvironmentDesc(props.workEnvironment, t)}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_leadership_relation')}</span>
<span className="font-medium text-gray-800">{getLeadershipDesc(props.leadership, t)}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_colleague_relationship')}</span>
<span className="font-medium text-gray-800">{getTeamworkDesc(props.teamwork, t)}</span>
</div>
<div className="bg-gray-50 p-3 rounded-lg flex justify-between">
<span className="text-sm text-gray-500">{t('share_canteen_quality')}</span>
<span className="font-medium text-gray-800">{getCanteenDesc(props.canteen, t)}</span>
</div>
</div>
</div>
{/* 教育背景 */}
<div className="col-span-2 mt-2">
<h2 className="font-bold text-gray-800 text-lg pb-2 mb-3 border-b border-gray-200 flex items-center">
<span className="mr-2">📚</span> {t('share_education_and_experience')}
</h2>
<div className="grid grid-cols-2 gap-4">
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_highest_degree')}</div>
<div className="font-medium text-gray-800 mt-1">{getDegreeDesc(props.degreeType, t)}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_school_type_label')}</div>
<div className="font-medium text-gray-800 mt-1">{getSchoolTypeDesc(props.schoolType, props.degreeType, t)}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_work_years_label')}</div>
<div className="font-medium text-gray-800 mt-1">{getWorkYearsDesc(props.workYears, t)}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-sm text-gray-500">{t('share_contract_type_label')}</div>
<div className="font-medium text-gray-800 mt-1">{getJobStabilityDesc(props.jobStability, t)}</div>
</div>
</div>
</div>
{/* 结论 */}
<div className="col-span-2 mt-4">
<div className="rounded-lg bg-gradient-to-r from-blue-50 to-purple-50 p-6 border border-gray-200">
<h2 className="font-bold text-gray-800 text-lg mb-3 flex items-center">
<span className="mr-2">💎</span> {t('share_final_assessment')}
</h2>
<div className="flex items-center mb-3">
<div className="text-4xl mr-3">{getEmoji(parseFloat(props.value))}</div>
<div className="text-xl font-bold" style={{ color: getColorFromClassName(props.assessmentColor) }}>
{props.value} - {t(getAssessmentKey(props.assessment))}
</div>
</div>
<p className="text-gray-700">
{parseFloat(props.value) < 1.0
? t('share_value_low')
: parseFloat(props.value) <= 2.0
? t('share_value_medium')
: t('share_value_high')
}
</p>
</div>
</div>
</div>
</div>
</div>
{/* 页脚 */}
<div className="bg-gray-50 py-4 px-6 border-t border-gray-200">
<div className="flex justify-between items-center">
<div className="flex flex-col">
<div className="text-sm font-medium text-gray-700">{t('share_custom_made')}</div>
<div className="text-sm text-gray-500">worthjob.zippland.com</div>
{/* 页脚 */}
<div className="bg-gray-50 py-4 px-6 border-t border-gray-200">
<div className="flex justify-between items-center">
<div className="flex items-center">
<img
src="/title.png"
alt="Job Worth Calculator"
className="h-20 mr-3"
/>
<div className="flex flex-col">
<div className="text-sm font-medium text-gray-700">{t('share_custom_made')}</div>
<div className="text-sm text-gray-500">worthjob.zippland.com</div>
</div>
</div>
<img
src="/website.png"
alt=""
className="h-16 w-16 opacity-85"
/>
</div>
<img
src="/website.png"
alt=""
className="h-16 w-16 opacity-85"
/>
</div>
</div>
</div>
</div>
</div>
)}
</div>
);
};

View File

@@ -1,373 +1,305 @@
"use client";
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { Wallet, Github, FileText, Book, History, Eye } from 'lucide-react'; // 添加新图标
import { Wallet, Github, FileText, Book, History, Eye , Star} from 'lucide-react'; // 添加新图标
import Link from 'next/link'; // 导入Link组件用于导航
import { useLanguage } from './LanguageContext';
import { LanguageSwitcher } from './LanguageSwitcher';
import { countryNames } from './LanguageContext';
// 定义PPP转换因子映射表
const pppFactors: Record<string, { name: string, factor: number }> = {
'AF': { name: '阿富汗', factor: 18.71 },
'AO': { name: '安哥拉', factor: 167.66 },
'AL': { name: '阿尔巴尼亚', factor: 41.01 },
'AR': { name: '阿根廷', factor: 28.67 },
'AM': { name: '亚美尼亚', factor: 157.09 },
'AG': { name: '安提瓜和巴布达', factor: 2.06 },
'AU': { name: '澳大利亚', factor: 1.47 },
'AT': { name: '奥地利', factor: 0.76 },
'AZ': { name: '阿塞拜疆', factor: 0.50 },
'BI': { name: '布隆迪', factor: 680.41 },
'BE': { name: '比利时', factor: 0.75 },
'BJ': { name: '贝宁', factor: 211.97 },
'BF': { name: '布基纳法索', factor: 209.84 },
'BD': { name: '孟加拉国', factor: 32.81 },
'BG': { name: '保加利亚', factor: 0.70 },
'BH': { name: '巴林', factor: 0.18 },
'BS': { name: '巴哈马', factor: 0.88 },
'BA': { name: '波斯尼亚和黑塞哥维那', factor: 0.66 },
'BY': { name: '白俄罗斯', factor: 0.77 },
'BZ': { name: '伯利兹', factor: 1.37 },
'BO': { name: '玻利维亚', factor: 2.60 },
'BR': { name: '巴西', factor: 2.36 },
'BB': { name: '巴巴多斯', factor: 2.24 },
'BN': { name: '文莱达鲁萨兰国', factor: 0.58 },
'BT': { name: '不丹', factor: 20.11 },
'BW': { name: '博茨瓦纳', factor: 4.54 },
'CF': { name: '中非共和国', factor: 280.19 },
'CA': { name: '加拿大', factor: 1.21 },
'CH': { name: '瑞士', factor: 1.14 },
'CL': { name: '智利', factor: 418.43 },
'CN': { name: '中国', factor: 4.19 },
'CI': { name: '科特迪瓦', factor: 245.25 },
'CM': { name: '喀麦隆', factor: 228.75 },
'CD': { name: '刚果(金)', factor: 911.27 },
'CG': { name: '刚果(布)', factor: 312.04 },
'CO': { name: '哥伦比亚', factor: 1352.79 },
'KM': { name: '科摩罗', factor: 182.34 },
'CV': { name: '佛得角', factor: 46.51 },
'CR': { name: '哥斯达黎加', factor: 335.86 },
'CY': { name: '塞浦路斯', factor: 0.61 },
'CZ': { name: '捷克共和国', factor: 12.66 },
'DE': { name: '德国', factor: 0.75 },
'DJ': { name: '吉布提', factor: 105.29 },
'DM': { name: '多米尼克', factor: 1.69 },
'DK': { name: '丹麦', factor: 6.60 },
'DO': { name: '多米尼加共和国', factor: 22.90 },
'DZ': { name: '阿尔及利亚', factor: 37.24 },
'EC': { name: '厄瓜多尔', factor: 0.51 },
'EG': { name: '阿拉伯埃及共和国', factor: 4.51 },
'ES': { name: '西班牙', factor: 0.62 },
'EE': { name: '爱沙尼亚', factor: 0.53 },
'ET': { name: '埃塞俄比亚', factor: 12.11 },
'FI': { name: '芬兰', factor: 0.84 },
'FJ': { name: '斐济', factor: 0.91 },
'FR': { name: '法国', factor: 0.73 },
'GA': { name: '加蓬', factor: 265.46 },
'GB': { name: '英国', factor: 0.70 },
'GE': { name: '格鲁吉亚', factor: 0.90 },
'GH': { name: '加纳', factor: 2.33 },
'GN': { name: '几内亚', factor: 4053.64 },
'GM': { name: '冈比亚', factor: 17.79 },
'GW': { name: '几内亚比绍共和国', factor: 214.86 },
'GQ': { name: '赤道几内亚', factor: 229.16 },
'GR': { name: '希腊', factor: 0.54 },
'GD': { name: '格林纳达', factor: 1.64 },
'GT': { name: '危地马拉', factor: 4.01 },
'GY': { name: '圭亚那', factor: 73.60 },
'HK': { name: '中国香港特别行政区', factor: 6.07 },
'HN': { name: '洪都拉斯', factor: 10.91 },
'HR': { name: '克罗地亚', factor: 3.21 },
'HT': { name: '海地', factor: 40.20 },
'HU': { name: '匈牙利', factor: 148.01 },
'ID': { name: '印度尼西亚', factor: 4673.65 },
'IN': { name: '印度', factor: 21.99 },
'IE': { name: '爱尔兰', factor: 0.78 },
'IR': { name: '伊朗伊斯兰共和国', factor: 30007.63 },
'IQ': { name: '伊拉克', factor: 507.58 },
'IS': { name: '冰岛', factor: 145.34 },
'IL': { name: '以色列', factor: 3.59 },
'IT': { name: '意大利', factor: 0.66 },
'JM': { name: '牙买加', factor: 72.03 },
'JO': { name: '约旦', factor: 0.29 },
'JP': { name: '日本', factor: 102.84 },
'KZ': { name: '哈萨克斯坦', factor: 139.91 },
'KE': { name: '肯尼亚', factor: 43.95 },
'KG': { name: '吉尔吉斯斯坦', factor: 18.28 },
'KH': { name: '柬埔寨', factor: 1400.09 },
'KI': { name: '基里巴斯', factor: 1.00 },
'KN': { name: '圣基茨和尼维斯', factor: 1.92 },
'KR': { name: '大韩民国', factor: 861.82 },
'LA': { name: '老挝', factor: 2889.36 },
'LB': { name: '黎巴嫩', factor: 1414.91 },
'LR': { name: '利比里亚', factor: 0.41 },
'LY': { name: '利比亚', factor: 0.48 },
'LC': { name: '圣卢西亚', factor: 1.93 },
'LK': { name: '斯里兰卡', factor: 51.65 },
'LS': { name: '莱索托', factor: 5.90 },
'LT': { name: '立陶宛', factor: 0.45 },
'LU': { name: '卢森堡', factor: 0.86 },
'LV': { name: '拉脱维亚', factor: 0.48 },
'MO': { name: '中国澳门特别行政区', factor: 5.18 },
'MA': { name: '摩洛哥', factor: 3.92 },
'MD': { name: '摩尔多瓦', factor: 6.06 },
'MG': { name: '马达加斯加', factor: 1178.10 },
'MV': { name: '马尔代夫', factor: 8.35 },
'MX': { name: '墨西哥', factor: 9.52 },
'MK': { name: '北马其顿', factor: 18.83 },
'ML': { name: '马里', factor: 211.41 },
'MT': { name: '马耳他', factor: 0.57 },
'MM': { name: '缅甸', factor: 417.35 },
'ME': { name: '黑山', factor: 0.33 },
'MN': { name: '蒙古', factor: 931.67 },
'MZ': { name: '莫桑比克', factor: 24.05 },
'MR': { name: '毛里塔尼亚', factor: 12.01 },
'MU': { name: '毛里求斯', factor: 16.52 },
'MW': { name: '马拉维', factor: 298.82 },
'MY': { name: '马来西亚', factor: 1.57 },
'NA': { name: '纳米比亚', factor: 7.40 },
'NE': { name: '尼日尔', factor: 257.60 },
'NG': { name: '尼日利亚', factor: 144.27 },
'NI': { name: '尼加拉瓜', factor: 11.75 },
'NL': { name: '荷兰', factor: 0.77 },
'NO': { name: '挪威', factor: 10.03 },
'NP': { name: '尼泊尔', factor: 33.52 },
'NZ': { name: '新西兰', factor: 1.45 },
'PK': { name: '巴基斯坦', factor: 38.74 },
'PA': { name: '巴拿马', factor: 0.46 },
'PE': { name: '秘鲁', factor: 1.80 },
'PH': { name: '菲律宾', factor: 19.51 },
'PG': { name: '巴布亚新几内亚', factor: 2.11 },
'PL': { name: '波兰', factor: 1.78 },
'PR': { name: '波多黎各', factor: 0.92 },
'PT': { name: '葡萄牙', factor: 0.57 },
'PY': { name: '巴拉圭', factor: 2575.54 },
'PS': { name: '约旦河西岸和加沙', factor: 0.57 },
'QA': { name: '卡塔尔', factor: 2.06 },
'RO': { name: '罗马尼亚', factor: 1.71 },
'RU': { name: '俄罗斯联邦', factor: 25.88 },
'RW': { name: '卢旺达', factor: 339.88 },
'SA': { name: '沙特阿拉伯', factor: 1.61 },
'SD': { name: '苏丹', factor: 21.85 },
'SN': { name: '塞内加尔', factor: 245.98 },
'SG': { name: '新加坡', factor: 0.84 },
'SB': { name: '所罗门群岛', factor: 7.08 },
'SL': { name: '塞拉利昂', factor: 2739.26 },
'SV': { name: '萨尔瓦多', factor: 0.45 },
'SO': { name: '索马里', factor: 9107.78 },
'RS': { name: '塞尔维亚', factor: 41.13 },
'ST': { name: '圣多美和普林西比', factor: 10.94 },
'SR': { name: '苏里南', factor: 3.55 },
'SK': { name: '斯洛伐克共和国', factor: 0.53 },
'SI': { name: '斯洛文尼亚', factor: 0.56 },
'SE': { name: '瑞典', factor: 8.77 },
'SZ': { name: '斯威士兰', factor: 6.36 },
'SC': { name: '塞舌尔', factor: 7.82 },
'TC': { name: '特克斯科斯群岛', factor: 1.07 },
'TD': { name: '乍得', factor: 220.58 },
'TG': { name: '多哥', factor: 236.83 },
'TH': { name: '泰国', factor: 12.34 },
'TJ': { name: '塔吉克斯坦', factor: 2.30 },
'TL': { name: '东帝汶', factor: 0.41 },
'TT': { name: '特立尼达和多巴哥', factor: 4.15 },
'TN': { name: '突尼斯', factor: 0.91 },
'TR': { name: '土耳其', factor: 2.13 },
'TV': { name: '图瓦卢', factor: 1.29 },
'TZ': { name: '坦桑尼亚', factor: 888.32 },
'UG': { name: '乌干达', factor: 1321.35 },
'UA': { name: '乌克兰', factor: 7.69 },
'UY': { name: '乌拉圭', factor: 28.45 },
'US': { name: '美国', factor: 1.00 },
'UZ': { name: '乌兹别克斯坦', factor: 2297.17 },
'VC': { name: '圣文森特和格林纳丁斯', factor: 1.54 },
'VN': { name: '越南', factor: 7473.67 },
'VU': { name: '瓦努阿图', factor: 110.17 },
'XK': { name: '科索沃', factor: 0.33 },
'ZA': { name: '南非', factor: 6.93 },
'ZM': { name: '赞比亚', factor: 5.59 },
'ZW': { name: '津巴布韦', factor: 24.98 }
const pppFactors: Record<string, number> = {
'AF': 18.71,
'AO': 167.66,
'AL': 41.01,
'AR': 28.67,
'AM': 157.09,
'AG': 2.06,
'AU': 1.47,
'AT': 0.76,
'AZ': 0.50,
'BI': 680.41,
'BE': 0.75,
'BJ': 211.97,
'BF': 209.84,
'BD': 32.81,
'BG': 0.70,
'BH': 0.18,
'BS': 0.88,
'BA': 0.66,
'BY': 0.77,
'BZ': 1.37,
'BO': 2.60,
'BR': 2.36,
'BB': 2.24,
'BN': 0.58,
'BT': 20.11,
'BW': 4.54,
'CF': 280.19,
'CA': 1.21,
'CH': 1.14,
'CL': 418.43,
'CN': 4.19,
'CI': 245.25,
'CM': 228.75,
'CD': 911.27,
'CG': 312.04,
'CO': 1352.79,
'KM': 182.34,
'CV': 46.51,
'CR': 335.86,
'CY': 0.61,
'CZ': 12.66,
'DE': 0.75,
'DJ': 105.29,
'DM': 1.69,
'DK': 6.60,
'DO': 22.90,
'DZ': 37.24,
'EC': 0.51,
'EG': 4.51,
'ES': 0.62,
'EE': 0.53,
'ET': 12.11,
'FI': 0.84,
'FJ': 0.91,
'FR': 0.73,
'GA': 265.46,
'GB': 0.70,
'GE': 0.90,
'GH': 2.33,
'GN': 4053.64,
'GM': 17.79,
'GW': 214.86,
'GQ': 229.16,
'GR': 0.54,
'GD': 1.64,
'GT': 4.01,
'GY': 73.60,
'HK': 6.07,
'HN': 10.91,
'HR': 3.21,
'HT': 40.20,
'HU': 148.01,
'ID': 4673.65,
'IN': 21.99,
'IE': 0.78,
'IR': 30007.63,
'IQ': 507.58,
'IS': 145.34,
'IL': 3.59,
'IT': 0.66,
'JM': 72.03,
'JO': 0.29,
'JP': 102.84,
'KZ': 139.91,
'KE': 43.95,
'KG': 18.28,
'KH': 1400.09,
'KI': 1.00,
'KN': 1.92,
'KR': 861.82,
'LA': 2889.36,
'LB': 1414.91,
'LR': 0.41,
'LY': 0.48,
'LC': 1.93,
'LK': 51.65,
'LS': 5.90,
'LT': 0.45,
'LU': 0.86,
'LV': 0.48,
'MO': 5.18,
'MA': 3.92,
'MD': 6.06,
'MG': 1178.10,
'MV': 8.35,
'MX': 9.52,
'MK': 18.83,
'ML': 211.41,
'MT': 0.57,
'MM': 417.35,
'ME': 0.33,
'MN': 931.67,
'MZ': 24.05,
'MR': 12.01,
'MU': 16.52,
'MW': 298.82,
'MY': 1.57,
'NA': 7.40,
'NE': 257.60,
'NG': 144.27,
'NI': 11.75,
'NL': 0.77,
'NO': 10.03,
'NP': 33.52,
'NZ': 1.45,
'PK': 38.74,
'PA': 0.46,
'PE': 1.80,
'PH': 19.51,
'PG': 2.11,
'PL': 1.78,
'PR': 0.92,
'PT': 0.57,
'PY': 2575.54,
'PS': 0.57,
'QA': 2.06,
'RO': 1.71,
'RU': 25.88,
'RW': 339.88,
'SA': 1.61,
'SD': 21.85,
'SN': 245.98,
'SG': 0.84,
'SB': 7.08,
'SL': 2739.26,
'SV': 0.45,
'SO': 9107.78,
'RS': 41.13,
'ST': 10.94,
'SR': 3.55,
'SK': 0.53,
'SI': 0.56,
'SE': 8.77,
'SZ': 6.36,
'SC': 7.82,
'TC': 1.07,
'TD': 220.58,
'TG': 236.83,
'TH': 12.34,
'TJ': 2.30,
'TL': 0.41,
'TT': 4.15,
'TN': 0.91,
'TR': 2.13,
'TV': 1.29,
'TW': 13.85,
'TZ': 888.32,
'UG': 1321.35,
'UA': 7.69,
'UY': 28.45,
'US': 1.00,
'UZ': 2297.17,
'VC': 1.54,
'VN': 7473.67,
'VU': 110.17,
'XK': 0.33,
'ZA': 6.93,
'ZM': 5.59,
'ZW': 24.98
};
// 为英文界面添加国家名称
const countryNamesEn: Record<string, string> = {
'AF': 'Afghanistan',
'AO': 'Angola',
'AL': 'Albania',
'AR': 'Argentina',
'AM': 'Armenia',
'AG': 'Antigua and Barbuda',
'AU': 'Australia',
'AT': 'Austria',
'AZ': 'Azerbaijan',
'BI': 'Burundi',
'BE': 'Belgium',
'BJ': 'Benin',
'BF': 'Burkina Faso',
'BD': 'Bangladesh',
'BG': 'Bulgaria',
'BH': 'Bahrain',
'BS': 'Bahamas',
'BA': 'Bosnia and Herzegovina',
'BY': 'Belarus',
'BZ': 'Belize',
'BO': 'Bolivia',
'BR': 'Brazil',
'BB': 'Barbados',
'BN': 'Brunei Darussalam',
'BT': 'Bhutan',
'BW': 'Botswana',
'CF': 'Central African Republic',
'CA': 'Canada',
'CH': 'Switzerland',
'CL': 'Chile',
'CN': 'China',
'CI': 'Côte d\'Ivoire',
'CM': 'Cameroon',
'CD': 'Congo (DRC)',
'CG': 'Congo (Republic)',
'CO': 'Colombia',
'KM': 'Comoros',
'CV': 'Cape Verde',
'CR': 'Costa Rica',
'CY': 'Cyprus',
'CZ': 'Czech Republic',
'DE': 'Germany',
'DJ': 'Djibouti',
'DM': 'Dominica',
'DK': 'Denmark',
'DO': 'Dominican Republic',
'DZ': 'Algeria',
'EC': 'Ecuador',
'EG': 'Egypt',
'ES': 'Spain',
'EE': 'Estonia',
'ET': 'Ethiopia',
'FI': 'Finland',
'FJ': 'Fiji',
'FR': 'France',
'GA': 'Gabon',
'GB': 'United Kingdom',
'GE': 'Georgia',
'GH': 'Ghana',
'GN': 'Guinea',
'GM': 'Gambia',
'GW': 'Guinea-Bissau',
'GQ': 'Equatorial Guinea',
'GR': 'Greece',
'GD': 'Grenada',
'GT': 'Guatemala',
'GY': 'Guyana',
'HK': 'Hong Kong SAR',
'HN': 'Honduras',
'HR': 'Croatia',
'HT': 'Haiti',
'HU': 'Hungary',
'ID': 'Indonesia',
'IN': 'India',
'IE': 'Ireland',
'IR': 'Iran',
'IQ': 'Iraq',
'IS': 'Iceland',
'IL': 'Israel',
'IT': 'Italy',
'JM': 'Jamaica',
'JO': 'Jordan',
'JP': 'Japan',
'KZ': 'Kazakhstan',
'KE': 'Kenya',
'KG': 'Kyrgyzstan',
'KH': 'Cambodia',
'KI': 'Kiribati',
'KN': 'St. Kitts and Nevis',
'KR': 'South Korea',
'LA': 'Laos',
'LB': 'Lebanon',
'LR': 'Liberia',
'LY': 'Libya',
'LC': 'St. Lucia',
'LK': 'Sri Lanka',
'LS': 'Lesotho',
'LT': 'Lithuania',
'LU': 'Luxembourg',
'LV': 'Latvia',
'MO': 'Macao SAR',
'MA': 'Morocco',
'MD': 'Moldova',
'MG': 'Madagascar',
'MV': 'Maldives',
'MX': 'Mexico',
'MK': 'North Macedonia',
'ML': 'Mali',
'MT': 'Malta',
'MM': 'Myanmar',
'ME': 'Montenegro',
'MN': 'Mongolia',
'MZ': 'Mozambique',
'MR': 'Mauritania',
'MU': 'Mauritius',
'MW': 'Malawi',
'MY': 'Malaysia',
'NA': 'Namibia',
'NE': 'Niger',
'NG': 'Nigeria',
'NI': 'Nicaragua',
'NL': 'Netherlands',
'NO': 'Norway',
'NP': 'Nepal',
'NZ': 'New Zealand',
'PK': 'Pakistan',
'PA': 'Panama',
'PE': 'Peru',
'PH': 'Philippines',
'PG': 'Papua New Guinea',
'PL': 'Poland',
'PR': 'Puerto Rico',
'PT': 'Portugal',
'PY': 'Paraguay',
'PS': 'West Bank and Gaza',
'QA': 'Qatar',
'RO': 'Romania',
'RU': 'Russia',
'RW': 'Rwanda',
'SA': 'Saudi Arabia',
'SD': 'Sudan',
'SN': 'Senegal',
'SG': 'Singapore',
'SB': 'Solomon Islands',
'SL': 'Sierra Leone',
'SV': 'El Salvador',
'SO': 'Somalia',
'RS': 'Serbia',
'ST': 'São Tomé and Principe',
'SR': 'Suriname',
'SK': 'Slovak Republic',
'SI': 'Slovenia',
'SE': 'Sweden',
'SZ': 'Eswatini',
'SC': 'Seychelles',
'TC': 'Turks and Caicos Islands',
'TD': 'Chad',
'TG': 'Togo',
'TH': 'Thailand',
'TJ': 'Tajikistan',
'TL': 'Timor-Leste',
'TT': 'Trinidad and Tobago',
'TN': 'Tunisia',
'TR': 'Turkey',
'TV': 'Tuvalu',
'TZ': 'Tanzania',
'UG': 'Uganda',
'UA': 'Ukraine',
'UY': 'Uruguay',
'US': 'United States',
'UZ': 'Uzbekistan',
'VC': 'St. Vincent and the Grenadines',
'VN': 'Vietnam',
'VU': 'Vanuatu',
'XK': 'Kosovo',
'ZA': 'South Africa',
'ZM': 'Zambia',
'ZW': 'Zimbabwe'
// 添加各国货币符号映射
const currencySymbols: Record<string, string> = {
'AF': '؋', // 阿富汗尼
'AL': 'L', // 阿尔巴尼亚列克
'DZ': 'د.ج', // 阿尔及利亚第纳尔
'AO': 'Kz', // 安哥拉宽扎
'AR': '$', // 阿根廷比索
'AM': '֏', // 亚美尼亚德拉姆
'AU': 'A$', // 澳大利亚元
'AT': '€', // 欧元
'AZ': '₼', // 阿塞拜疆马纳特
'BI': 'FBu', // 布隆迪法郎
'BE': '€', // 欧元
'BJ': 'CFA', // 西非法郎
'BF': 'CFA', // 西非法郎
'BD': '৳', // 孟加拉塔卡
'BG': 'лв', // 保加利亚列弗
'BH': '.د.ب', // 巴林第纳尔
'BS': 'B$', // 巴哈马元
'BA': 'KM', // 波黑可兑换马克
'BY': 'Br', // 白俄罗斯卢布
'BZ': 'BZ$', // 伯利兹元
'BO': 'Bs', // 玻利维亚诺
'BR': 'R$', // 巴西雷亚尔
'BB': 'Bds$', // 巴巴多斯元
'BN': 'B$', // 文莱元
'BT': 'Nu.', // 不丹努扎姆
'BW': 'P', // 博茨瓦纳普拉
'CA': 'C$', // 加拿大元
'CH': 'CHF', // 瑞士法郎
'CL': 'CLP$', // 智利比索
'CN': '¥', // 人民币
'CI': 'CFA', // 西非法郎
'CM': 'FCFA', // 中非法郎
'CD': 'FC', // 刚果法郎
'CG': 'FCFA', // 中非法郎
'CO': 'Col$', // 哥伦比亚比索
'CR': '₡', // 哥斯达黎加科朗
'CY': '€', // 欧元
'CZ': 'Kč', // 捷克克朗
'DE': '€', // 欧元
'DK': 'kr', // 丹麦克朗
'DO': 'RD$', // 多米尼加比索
'EC': '$', // 美元(厄瓜多尔使用美元)
'EG': 'E£', // 埃及镑
'ES': '€', // 欧元
'EE': '€', // 欧元
'ET': 'Br', // 埃塞俄比亚比尔
'FI': '€', // 欧元
'FJ': 'FJ$', // 斐济元
'FR': '€', // 欧元
'GB': '£', // 英镑
'GE': '₾', // 格鲁吉亚拉里
'GH': '₵', // 加纳塞地
'GR': '€', // 欧元
'GT': 'Q', // 危地马拉格查尔
'HK': 'HK$', // 港元
'HN': 'L', // 洪都拉斯伦皮拉
'HR': '€', // 欧元克罗地亚自2023年加入欧元区
'HU': 'Ft', // 匈牙利福林
'ID': 'Rp', // 印尼盾
'IN': '₹', // 印度卢比
'IE': '€', // 欧元
'IR': '﷼', // 伊朗里亚尔
'IQ': 'ع.د', // 伊拉克第纳尔
'IS': 'kr', // 冰岛克朗
'IL': '₪', // 以色列新谢克尔
'IT': '€', // 欧元
'JM': 'J$', // 牙买加元
'JO': 'JD', // 约旦第纳尔
'JP': '¥', // 日元
'KE': 'KSh', // 肯尼亚先令
'KR': '₩', // 韩元
'KW': 'د.ك', // 科威特第纳尔
'LB': 'L£', // 黎巴嫩镑
'LK': 'Rs', // 斯里兰卡卢比
'LT': '€', // 欧元
'LU': '', // 欧元
'LV': '', // 欧元
'MA': 'د.م.', // 摩洛哥迪拉姆
'MX': 'Mex$', // 墨西哥比索
'MY': 'RM', // 马来西亚林吉特
'NG': '₦', // 尼日利亚奈拉
'NL': '€', // 欧元
'NO': 'kr', // 挪威克朗
'NP': 'रू', // 尼泊尔卢比
'NZ': 'NZ$', // 新西兰元
'PK': '₨', // 巴基斯坦卢比
'PA': 'B/.', // 巴拿马巴波亚
'PE': 'S/.', // 秘鲁索尔
'PH': '₱', // 菲律宾比索
'PL': 'zł', // 波兰兹罗提
'PT': '', // 欧元
'QA': 'ر.ق', // 卡塔尔里亚尔
'RO': 'lei', // 罗马尼亚列伊
'RU': '₽', // 俄罗斯卢布
'SA': 'ر.س', // 沙特里亚尔
'SG': 'S$', // 新加坡元
'SK': '€', // 欧元
'SI': '€', // 欧元
'SE': 'kr', // 瑞典克朗
'TH': '฿', // 泰铢
'TR': '₺', // 土耳其里拉
'TW': 'NT$', // 新台币
'UA': '₴', // 乌克兰格里夫纳
'US': '$', // 美元
'UY': '$U', // 乌拉圭比索
'VN': '₫', // 越南盾
'ZA': 'R', // 南非兰特
// 默认其他国家使用美元符号
};
// 定义历史记录项的接口
@@ -479,6 +411,26 @@ const SalaryCalculator = () => {
const [showPPPInput, setShowPPPInput] = useState(false);
// 修改为国家代码,默认为中国
const [selectedCountry, setSelectedCountry] = useState<string>('CN');
// 初始化时从localStorage加载国家设置
useEffect(() => {
// 从本地存储读取国家设置
if (typeof window !== 'undefined') {
const savedCountry = localStorage.getItem('selectedCountry');
if (savedCountry) {
setSelectedCountry(savedCountry);
}
}
}, []);
// 当国家选择改变时保存到localStorage
const handleCountryChange = (countryCode: string) => {
setSelectedCountry(countryCode);
if (typeof window !== 'undefined') {
localStorage.setItem('selectedCountry', countryCode);
}
};
const [result, setResult] = useState<Result | null>(null);
const [showPPPList, setShowPPPList] = useState(false);
const [assessment, setAssessment] = useState("");
@@ -505,13 +457,37 @@ const SalaryCalculator = () => {
// 延迟检查busuanzi是否已加载
const timer = setTimeout(() => {
const pv = document.getElementById('busuanzi_value_site_pv');
const uv = document.getElementById('busuanzi_value_site_uv');
if (pv && pv.innerText !== '') {
// 直接在现有数字上加上1700000原seeyoufarm统计数据
const currentCount = parseInt(pv.innerText, 10) || 0;
pv.innerText = (currentCount + 1700000).toString();
// 同时增加访客数的历史数据
if (uv && uv.innerText !== '') {
const currentUV = parseInt(uv.innerText, 10) || 0;
uv.innerText = (currentUV + 250000).toString();
}
setVisitorVisible(true);
} else {
// 如果未加载,再次尝试
const retryTimer = setTimeout(() => {
const pv = document.getElementById('busuanzi_value_site_pv');
const uv = document.getElementById('busuanzi_value_site_uv');
if (pv && pv.innerText !== '') {
// 直接在现有数字上加上1700000原seeyoufarm统计数据
const currentCount = parseInt(pv.innerText, 10) || 0;
pv.innerText = (currentCount + 1700000).toString();
// 同时增加访客数的历史数据
if (uv && uv.innerText !== '') {
const currentUV = parseInt(uv.innerText, 10) || 0;
uv.innerText = (currentUV + 1300000).toString();
}
setVisitorVisible(true);
}
}, 2000);
@@ -565,7 +541,7 @@ const SalaryCalculator = () => {
// 应用PPP转换因子标准化薪资
// 如果选择了非中国地区使用选定国家的PPP否则使用中国默认值4.19
const isNonChina = selectedCountry !== 'CN';
const pppFactor = isNonChina ? pppFactors[selectedCountry]?.factor || 4.19 : 4.19;
const pppFactor = isNonChina ? pppFactors[selectedCountry] || 4.19 : 4.19;
const standardizedSalary = Number(formData.salary) * (4.19 / pppFactor);
return standardizedSalary / workingDays; // 除 0 不管, Infinity(爽到爆炸)!
@@ -577,7 +553,7 @@ const SalaryCalculator = () => {
const isNonChina = selectedCountry !== 'CN';
if (isNonChina) {
// 非中国地区,转回本地货币
const pppFactor = pppFactors[selectedCountry]?.factor || 4.19;
const pppFactor = pppFactors[selectedCountry] || 4.19;
return (dailySalaryInCNY * pppFactor / 4.19).toFixed(2);
} else {
return dailySalaryInCNY.toFixed(2);
@@ -690,6 +666,18 @@ const SalaryCalculator = () => {
if (value <= 4.0) return { text: t('rating_excellent'), color: "text-purple-500" };
return { text: t('rating_perfect'), color: "text-yellow-400" };
};
// 获取评级的翻译键,用于分享链接
const getValueAssessmentKey = () => {
if (!formData.salary) return 'rating_enter_salary';
if (value < 0.6) return 'rating_terrible';
if (value < 1.0) return 'rating_poor';
if (value <= 1.8) return 'rating_average';
if (value <= 2.5) return 'rating_good';
if (value <= 3.2) return 'rating_great';
if (value <= 4.0) return 'rating_excellent';
return 'rating_perfect';
};
const RadioGroup = ({ label, name, value, onChange, options }: {
label: string;
@@ -785,9 +773,12 @@ const SalaryCalculator = () => {
// 获取当前选择的国家名称(根据语言)
const getCountryName = useCallback((countryCode: string) => {
if (language === 'en') {
return countryNamesEn[countryCode] || pppFactors[countryCode]?.name || 'Unknown';
return countryNames.en[countryCode] || countryCode || 'Unknown';
}
return pppFactors[countryCode]?.name || 'Unknown';
if (language === 'ja') {
return countryNames.ja[countryCode] || countryCode || '不明';
}
return countryNames.zh[countryCode] || countryCode || '未知';
}, [language]);
// 保存当前记录到历史中
@@ -852,13 +843,30 @@ const SalaryCalculator = () => {
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
};
// 获取当前选择国家的货币符号
const getCurrencySymbol = useCallback((countryCode: string) => {
return currencySymbols[countryCode] || '$'; // 如果没有找到对应货币符号,默认使用美元符号
}, []);
return (
<div className="max-w-2xl mx-auto p-4 sm:p-6">
<div className="mb-4 text-center">
<h1 className="text-3xl md:text-4xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-purple-600 py-2">{t('title')}</h1>
<div className="mb-3">
<a
href="https://github.com/zippland/worth-calculator"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-gray-600 dark:text-gray-300 hover:text-blue-500 dark:hover:text-blue-400 transition-colors inline-flex items-center gap-1.5"
>
<Star className="h-3.5 w-3.5" />
{t('star_request')}
</a>
</div>
<div className="flex items-center justify-center gap-3 mb-2">
<p className="text-sm text-gray-500 dark:text-gray-400">{t('version')}</p>
<p className="text-sm text-gray-500 dark:text-gray-400">v5.6.1</p>
<a
href="https://github.com/zippland/worth-calculator"
target="_blank"
@@ -869,13 +877,13 @@ const SalaryCalculator = () => {
{t('github')}
</a>
<a
href="https://www.xiaohongshu.com/user/profile/6355d5c4000000001f0292bf"
href="https://www.xiaohongshu.com/user/profile/623e8b080000000010007721?xsec_token=YBzoLUB4HsSITTBOgPAXY-0Gvqvn3HqHpcDeA3sHhDh-M%3D&xsec_source=app_share&xhsshare=CopyLink&appuid=5c5d5259000000001d00ef04&apptime=1743400694&share_id=b9bfcd5090f9473daf5c1d1dc3eb0921&share_channel=copy_link"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-gray-500 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-400 transition-colors flex items-center gap-1"
>
<Book className="h-3.5 w-3.5" />
{language === 'zh' ? t('xiaohongshu') : 'Rednote'}
{t('xiaohongshu')}
</a>
{/* 仅在客户端渲染历史记录按钮 */}
{isBrowser && (
@@ -1032,7 +1040,9 @@ const SalaryCalculator = () => {
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{selectedCountry !== 'CN' ? t('annual_salary_foreign') : t('annual_salary_cny')}
{selectedCountry !== 'CN' ?
`${t('annual_salary')}(${getCurrencySymbol(selectedCountry)})` :
t('annual_salary_cny')}
</label>
<div className="flex items-center gap-2 mt-1">
<Wallet className="w-4 h-4 text-gray-500 dark:text-gray-400" />
@@ -1040,7 +1050,9 @@ const SalaryCalculator = () => {
type="number"
value={formData.salary}
onChange={(e) => handleInputChange('salary', e.target.value)}
placeholder={selectedCountry !== 'CN' ? t('salary_placeholder_foreign') : t('salary_placeholder_cny')}
placeholder={selectedCountry !== 'CN' ?
`${t('salary_placeholder')} ${getCurrencySymbol(selectedCountry)}` :
t('salary_placeholder_cny')}
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>
@@ -1057,22 +1069,25 @@ const SalaryCalculator = () => {
</span>
</label>
<select
id="country"
name="country"
value={selectedCountry}
onChange={(e) => setSelectedCountry(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"
onChange={(e) => handleCountryChange(e.target.value)}
className="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
{Object.keys(pppFactors).sort((a, b) => {
const nameA = getCountryName(a);
const nameB = getCountryName(b);
return nameA.localeCompare(nameB);
// 确保中国始终排在第一位
if (a === 'CN') return -1;
if (b === 'CN') return 1;
return getCountryName(a).localeCompare(getCountryName(b));
}).map(code => (
<option key={code} value={code}>
{getCountryName(code)} ({pppFactors[code].factor})
{getCountryName(code)} ({pppFactors[code].toFixed(2)})
</option>
))}
</select>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{t('selected_ppp')}: {pppFactors[selectedCountry]?.factor || 4.19}
{t('selected_ppp')}: {(pppFactors[selectedCountry] || 4.19).toFixed(2)}
</p>
</div>
@@ -1387,7 +1402,7 @@ const SalaryCalculator = () => {
<div>
<div className="text-sm font-medium text-gray-500 dark:text-gray-400">{t('average_daily_salary')}</div>
<div className="text-2xl font-semibold mt-1 text-gray-900 dark:text-white">
{selectedCountry !== 'CN' ? '$' : '¥'}{getDisplaySalary()}
{getCurrencySymbol(selectedCountry)}{getDisplaySalary()}
</div>
</div>
<div>
@@ -1406,7 +1421,7 @@ const SalaryCalculator = () => {
pathname: '/share',
query: {
value: value.toFixed(2),
assessment: getValueAssessment().text,
assessment: getValueAssessmentKey(),
assessmentColor: getValueAssessment().color,
cityFactor: formData.cityFactor,
workHours: formData.workHours,
@@ -1433,7 +1448,8 @@ const SalaryCalculator = () => {
jobStability: formData.jobStability,
bachelorType: formData.bachelorType,
countryCode: selectedCountry,
countryName: getCountryName(selectedCountry)
countryName: getCountryName(selectedCountry),
currencySymbol: getCurrencySymbol(selectedCountry)
}
}}
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors

BIN
public/title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

BIN
title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 KiB