哥们儿/姐妹儿,你们有没有对着工资条想过:你的工作到底值不值?每天两小时通勤、加班加到灵魂出窍、办公室氛围还贼压抑、偶尔只能靠“摸鱼”回血的灵魂… 它到底还值几个钱? 反正我是算不清这笔老板给的“窝囊费”换“生命值”的糊涂账了!

别纠结了!这篇教程手把手教你用 Docker 一键部署一个开源神器 —— Job Worth Calculator (工作性价比计算器)!这可不是简单看工资高低,它能帮你全面分析工作价值:把你的月薪/年薪、工作时长、通勤地狱、摸鱼回血时间、甚至工作环境系数(比如有没有养眼同事?)通通塞进科学公式,噼里啪啦算出个硬核性价比得分

Docker 部署有啥爽的? 简单!快速!省心!告别复杂环境配置,几条命令就能让你的专属“工作价值评估师”上线。算完你就知道,是老板在“合理定价”你的付出,还是你该考虑让“性价比上班”成为现实了!

花几分钟部署,算个明白账。你的时间和才华,值得被更精准地衡量!👇


📦 项目介绍:工作不止薪水,更看性价比!

📊 工作性价比计算器

这是一款基于 Next.js 构建的开源工具,通过多维度数据模型,科学评估你的工作值不值上

在线体验地址:https://job.aabcc.top/

✨ 项目亮点

  • 💰 全面评估:综合薪资、工时、通勤、环境等多个维度

  • 🌏 PPP换算:支持全球 190+ 国家薪资购买力对比

  • 👩‍🎓 个性化因素:学历和经验会影响得分结果

  • 📱 详细报告:自动生成图文分析结果,可分享/下载

  • 🌐 双语支持:界面支持中英文切换

  • 📱 移动友好:响应式页面,手机也能舒服测


🧮 计算方式一览:数据背后的逻辑

Job Worth Calculator 不是凭空“估价”,而是有一套成熟的评分公式支撑:

  • 标准化日薪:会根据购买力平价(PPP)调整不同国家的实际薪资水平

  • 生活平衡指数:考虑工作时间、通勤长度、远程选项等变量

  • 环境评价加权:如城市环境、工作氛围、团队支持等因素

  • 学历加分:你的教育背景越好,预期值越高

  • 经验校准:结合工作年限优化评估模型


🖥️ 如何使用:简单几步,测算你的“打工值”

部署完成后,你可以在浏览器中访问这个计算器,使用步骤如下:

  1. 输入你的年薪(税前/税后)

  2. 选择工作国家或地区

  3. 填写详细信息

    • 每周工作天数

    • 每天工作时长

    • 通勤单程时长

    • 是否远程办公

  4. 指定工作环境

    • 所在城市

    • 办公条件(如是否拥挤、氛围友好等)

    • 团队协作程度

  5. 输入个人背景

    • 学历(如大专、本科、硕士等)

    • 工作经验(年限)

  6. 查看结果

    • 得到一个“工作性价比分数”

    • 并附带详细解释 + 建议

  7. 生成报告

    • 一键生成 PDF 报告

    • 支持复制链接或下载保存

Docker部署 Job Worth Calculator 教程

以下示例基于飞牛NAS系统演示,其他系统请确保已安装最新版本 Docker 和 Docker Compose。


1. 连接 NAS 并准备目录

  • 开启飞牛NAS的 SSH 功能,用终端连接,并切换为 root 用户。
    fnOS open SSH.png

  • 新建项目文件夹,复制完整路径。

  • 终端内进入该目录(请替换为实际路径):

    # 将 /vol1/1000/job 换成你自己实际的文件夹路径
    
    cd /vol1/1000/job

2. 获取项目代码

git clone https://github.com/Zippland/worth-calculator.git

3. 编写 Dockerfile

  1. 编辑 Dockerfile:

    vi Dockerfile
  2. 按字母i键进入编辑模式,复制下面的配置文件,粘贴到终端,按Esc键退出编辑模式,输入:wq保存并退出。

    # 基础镜像:Node.js 18 + Alpine
    FROM node:18-alpine
    
    # 工作目录
    WORKDIR /app
    
    # 复制依赖文件并安装依赖
    COPY package*.json ./
    RUN npm install
    
    # 复制全部代码
    COPY . .
    
    # 构建生产版本
    RUN npm run build
    
    # 暴露端口
    EXPOSE 3000
    
    # 启动 Next.js
    CMD ["npm", "start"]

4. 编写 docker-compose.yml

  1. 编辑 docker-compose.yml:

    vi docker-compose.yml
  2. 按字母i键进入编辑模式,复制并修改下面的配置文件,粘贴到终端,按Esc键退出编辑模式,输入:wq保存并退出。

    (排版太挤的就先粘贴到文本文件内,修改后再使用,灵活应变。)

    services:
      worth-calculator:
        build: .
        container_name: worth-calculator
        ports:
          - "3000:3000"    # 左侧的3000端口是容器外部访问端口,可自行修改。
        restart: always

5. 关闭自动跳转限制

  1. 在该项目中作者设置了自动跳转到作者的域名,我们想要本地搭建自己的,所以需要禁用/改成我们自己的域名。

  2. 编辑 components/calculator.tsx

    vi components/calculator.tsx
  3. components/calculator.tsx 第 402-404 行(20250716这个日期内的版本),这段代码强制将你跳转到 https://worthjob.zippland.com,只允许本地调试。

  4. 找到约第 400 行附近的以下代码,注释或删除(演示是已经注释掉了),想对外发布并跳转到自己的域名可以自行修改。
    按字母i键进入编辑模式,修改默认的配置文件,按Esc键退出编辑模式,输入:wq保存并退出。

    // const hostname = window.location.hostname;
    // if (hostname !== 'worthjob.zippland.com' && hostname !== 'localhost' && !hostname.includes('127.0.0.1')) {
    //   window.location.href = 'https://worthjob.zippland.com' + window.location.pathname;
    // }

6. 构建并启动容器

在项目根目录执行(当前路径下):

docker compose up -d --build

构建过程可能较长,请耐心等待。


7. 查看运行状态

  1. 查看正在运行的项目容器:

    docker compose ps
    
    或
    
    docker-compose ps
  2. 查看正在运行的项目容器实时日志,按Ctrl+C中断查看:

    docker compose logs -f
    
    或
    
    docker-compose logs -f

8. 访问Job Worth Calculator应用

  1. 打开浏览器,以NAS的IP+设置的端口号进行访问。
    以本机为例:http://192.168.2.5:3000/

  2. 该工具当前支持多种语言,按照提示我们来试一下,计算一下牛马的工作性价比。

  3. 拉到底部点击 查看我的工作性价比报告

  4. 不忍直视啊兄弟们,要不提桶跑路吧。


自定义修改指南

🔄 每一次修改完后,别忘了重建项目,改造出属于自己的工作价值计算!

🛠 修改弹窗广告

  1. 这个是页面打开的弹窗广告区域,你可以直接改这里的内容:

  2. 文件路径:

    components/VerticalAd.tsx
  3. 下面是加上详细注释的文件内容,方便你手动修改。

    'use client';
    
    import { useState, useEffect } from 'react';
    import Image from 'next/image';
    import { X } from 'lucide-react'; // 引入关闭图标
    import { isAdEnabled, getAdLink } from '@/utils/adConfig'; // 获取广告启用状态和跳转地址
    
    export default function VerticalAd() {
      const [isVisible, setIsVisible] = useState(false);       // 控制是否显示弹窗
      const [imageExists, setImageExists] = useState(false);   // 检查广告图是否存在
    
      // 加载图片并检查是否存在
      useEffect(() => {
        const img = new window.Image();
        img.onload = () => setImageExists(true);
        img.onerror = () => setImageExists(false);
        img.src = '/mainpage.png'; // ✅ 替换这里即可更换图片,比如 '/myad.jpg' 或网络图 'https://xx.com/ad.jpg'
      }, []);
    
      // 检查是否启用广告 + 图片存在才显示
      useEffect(() => {
        if (!isAdEnabled() || !imageExists) {
          return;
        }
    
        // 延迟 1000ms 后显示弹窗广告
        const timer = setTimeout(() => {
          setIsVisible(true);
        }, 1000);
    
        return () => clearTimeout(timer);
      }, [imageExists]);
    
      // 点击关闭按钮逻辑
      const handleClose = () => {
        setIsVisible(false);
      };
    
      // 广告不显示则返回 null
      if (!isVisible) return null;
    
      return (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
          <div className="relative">
            {/* 关闭按钮 */}
            <button
              onClick={handleClose}
              className="absolute -top-2 -right-2 z-10 rounded-full bg-white p-1 shadow-lg hover:bg-gray-100 transition-colors"
              aria-label="关闭广告"
            >
              <X className="h-5 w-5 text-gray-700" />
            </button>
    
            {/* 广告区域,宽高比 3:5 */}
            <a
              href={getAdLink()} // ✅ 修改广告跳转地址:修改 utils/adConfig.ts 中的 getAdLink 函数
              target="_blank"
              rel="noopener noreferrer"
              className="block relative bg-gray-200 rounded-lg overflow-hidden shadow-xl hover:shadow-2xl transition-shadow duration-300 cursor-pointer"
              style={{
                width: 'min(300px, 60vw)',
                aspectRatio: '3/5'
              }}
            >
              {/* 弹窗广告图片 */}
              <Image
                src="/mainpage.png" // ✅ 替换为你自己的图片路径(public 文件夹内或网络地址)
                alt="广告"
                fill
                className="object-cover"
                priority
              />
            </a>
          </div>
        </div>
      );
    }
  4. ✏️ 如何手动替换广告内容?

    替换项

    修改位置

    示例

    📸 广告图片

    img.src = '/mainpage.png'<Image src=... />

    改为 /myad.jpghttps://你的图.jpg

    🔗 广告链接

    getAdLink()utils/adConfig.ts

    改为 https://你的推广页面

    ⏱️ 延迟时间

    setTimeout(() => ..., 1000)

    改为 3000 表示 3 秒后弹出

  5. ✅ 最终效果

    • 弹窗广告将在页面加载 1 秒后显示

    • 点击广告图跳转到你设置的推广地址

    • 你可随时手动关闭或修改图片路径、延迟时间

🛠 修改页面顶部广告

  1. 这个是页面最上方的横幅区域,你可以直接改这里的内容:

  2. 文件路径:

    components/HorizontalBanner.tsx
  3. 下面是加上详细注释的文件内容,方便你手动修改。

    'use client'; // 启用 Next.js 的客户端组件模式(才能使用 useEffect)
    
    import { useState, useEffect } from 'react';
    import Image from 'next/image';
    import { isAdEnabled, getAdLink } from '@/utils/adConfig'; // 从广告配置文件引入控制函数
    
    // 顶部横幅广告组件
    export default function HorizontalBanner() {
      const [imageExists, setImageExists] = useState(false); // 控制是否显示广告图
    
      useEffect(() => {
        // 加载图片,判断是否存在 banner.png
        const img = new window.Image();
        img.onload = () => setImageExists(true);   // 加载成功
        img.onerror = () => setImageExists(false); // 加载失败
        img.src = '/banner.png'; // ✅ 你只需要替换这个路径为你自己的图片,例如 '/mybanner.jpg'
      }, []);
    
      // 如果广告功能未开启(比如设置为 false),则不显示
      if (!isAdEnabled()) {
        return null;
      }
    
      // 如果图片加载失败,也不显示
      if (!imageExists) {
        return null;
      }
    
      return (
        <div className="w-full bg-gray-50 dark:bg-gray-900 py-2">
          <div className="max-w-4xl mx-auto px-4">
            {/* 横幅容器,点击后跳转到广告链接 */}
            <a
              href={getAdLink()} // ✅ 广告跳转地址:你只需要修改 utils/adConfig.ts 中的 getAdLink() 返回值
              target="_blank"
              rel="noopener noreferrer"
              className="block relative w-full bg-gray-200 dark:bg-gray-800 overflow-hidden hover:opacity-90 transition-opacity duration-300 cursor-pointer rounded-lg shadow-sm"
              style={{
                aspectRatio: '7/1' // 设置为横向长条比例(7:1)
              }}
            >
              {/* 显示广告图片 */}
              <Image
                src="/banner.png" // ✅ 你也可以替换为如 'https://yourdomain.com/ad.jpg'
                alt="横幅广告"
                fill // 全覆盖模式
                className="object-cover"
                priority // 提前加载
              />
            </a>
          </div>
        </div>
      );
    }

🛠 页面的标签标题

  1. 这个是页面的标签标题,你可以直接改这里的内容:

  2. 文件路径:

    app/layout.tsx
  3. 请将 app/layout.tsx 中这段原始代码:

    export const metadata: Metadata = {
      title: {
        default: "Job Worth Calculator",
        template: "%s | Job Worth Calculator"
      },
      ...
    };
  4. 参考以下内容修改(含详细中文注释):

    export const metadata: Metadata = {
      title: {
        // 设置默认标签标题,当页面没有单独指定 title 时使用
        default: "Job Worth Calculator",
    
        // 设置标题模板,会将页面单独设置的 title 插入到 %s 位置
        // 例如:子页面 title 为 "城市对比",最终显示为 "城市对比 | Job Worth Calculator"
        // 如果希望所有页面统一显示同一个标题,可删除这一项(template)
        template: "%s | Job Worth Calculator"
      },
    
      // 配置不同语言对应的路径(用于多语言切换,SEO)
      alternates: {
        languages: {
          "en-US": "/en",   // 英文路径映射为 /en
          "zh-CN": "/",     // 中文默认路径为 /
        },
      },
    
      // 页面描述,供搜索引擎/社交媒体预览使用
      description: "这b班上得值不值 - 计算你的工作性价比 | Job Worth Calculator - Calculate your job's value",
    
      // Google Search Console 的站点验证代码(用来验证你拥有这个网站)
      // 如果不使用 Google 站长工具,可以删除这部分
      verification: {
        google: "_OQGiIpYz87USAsgJV2C07-JJhQ8myV_4GoM1kDjFic",
      },
    };
  5. 或者改成下面的符合SEO优化的内容

    // 设置页面基础的 SEO 元数据
    export const metadata: Metadata = {
      // 浏览器标签标题(统一显示,不拼接模板)
      title: "我的收入价值评估器",
    
      // 页面描述:用于搜索引擎摘要展示
      description: "这b班上得值不值 - 计算你的工作性价比",
    
      // 支持多语言 URL(可选)
      alternates: {
        languages: {
          "en-US": "/en",
          "zh-CN": "/",
        },
      },
    
      // Google 搜索站长平台的验证字符串(如无需求可删除)
      verification: {
        google: "_OQGiIpYz87USAsgJV2C07-JJhQ8myV_4GoM1kDjFic",
      },
    };
  6. 🔄 修改后效果

    • 浏览器标签统一显示:我的收入价值评估器

    • 子页面不会再拼接 %s | Job Worth Calculator

    • 保留了 SEO 描述和语言切换支持

    • 可随时扩展 openGraphtwitter 等字段来提升分享效果

🛠 页面的页脚跳转图标

  1. 这个是页面的页脚跳转图标,你可以直接改这里的内容:

  2. 文件路径:

    app/layout.tsx
  3. 请将 app/layout.tsx 中的对应信息更改(贴出完整的带注释的原配置方便你修改)

    // 导入 Next.js 的类型定义 Metadata,用于页面元信息配置
    import type { Metadata } from "next";
    
    // 从本地引入字体功能
    import localFont from "next/font/local";
    
    // 引入全局样式
    import "./globals.css";
    
    // 引入语言上下文提供者,用于实现语言切换功能
    import { LanguageProvider } from "@/components/LanguageContext";
    
    // 引入 Vercel 分析组件(自动收集访问数据)
    import { Analytics } from "@vercel/analytics/next";
    
    // 加载本地自定义字体 Geist Sans
    const geistSans = localFont({
      src: "./fonts/GeistVF.woff",         // 字体文件路径
      variable: "--font-geist-sans",       // 自定义 CSS 变量名,用于后续 className 应用
      weight: "100 900",                   // 支持的字体粗细范围
    });
    
    // 加载本地自定义字体 Geist Mono(等宽字体)
    const geistMono = localFont({
      src: "./fonts/GeistMonoVF.woff",
      variable: "--font-geist-mono",
      weight: "100 900",
    });
    
    // 配置网站的元信息(SEO 相关)供 Next.js 使用
    export const metadata: Metadata = {
      title: {
        default: "Job Worth Calculator",                   // 默认标题
        template: "%s | Job Worth Calculator",             // 页面标题模板(用于子页面)
      },
      alternates: {
        languages: {
          "en-US": "/en",     // 英文路径映射
          "zh-CN": "/",       // 中文路径映射
        },
      },
      description: "这b班上得值不值 - 计算你的工作性价比 | Job Worth Calculator - Calculate your job's value", // 页面描述
      verification: {
        google: "_OQGiIpYz87USAsgJV2C07-JJhQ8myV_4GoM1kDjFic", // Google 搜索站长平台的验证字符串(如无需求可删除)
      },
    };
    
    // 根布局组件,所有页面会包裹在这个 layout 中
    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode; // 接收子组件
    }>) {
      return (
        <html lang="en"> {/* 设置 HTML 页面语言 */}
          <head>
            {/* Google 网站验证元标签 */}
            <meta name="google-site-verification" content="_OQGiIpYz87USAsgJV2C07-JJhQ8myV_4GoM1kDjFic" />
            {/* 百度网站验证元标签 */}
            <meta name="baidu-site-verification" content="codeva-pEoMg5F0Cv" />
            {/* Google AdSense 广告脚本(需替换为你自己的广告位 ID) */}
            <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7207313144293144" crossOrigin="anonymous"></script>
            {/* 不蒜子网页访问统计脚本 */}
            <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
          </head>
          <body
            className={`${geistSans.variable} ${geistMono.variable} antialiased`} // 应用自定义字体和抗锯齿优化
          >
            {/* 多语言上下文提供者包裹整个页面 */}
            <LanguageProvider>
              {children}
            </LanguageProvider>
    
            {/* Vercel 网站访问分析组件 */}
            <Analytics />
    
            {/* 用于页面底部预留空间(比如避免内容被 footer 遮挡) */}
            <div className="pb-8"></div>
    
            {/* 页面底部导航栏 */}
            <footer className="w-full py-3 border-t bg-white/90 dark:bg-gray-900/80 dark:border-gray-800/50">
              <div className="max-w-4xl mx-auto px-4">
                <div className="flex justify-center items-center gap-6 mb-2">
                  
                  {/* 链接 1:OfferSelect 工具,工作比价工具 */}
                  <a
                    href="https://offerselect.zippland.com/"
                    target="_blank"
                    rel="noopener noreferrer"
                    className="group relative flex flex-col items-center"
                  >
                    <div className="w-10 h-10 flex items-center justify-center rounded-full bg-gradient-to-br from-yellow-400 to-orange-500 shadow-sm transform transition-all duration-200 group-hover:scale-110">
                      {/* 图标 */}
                      <svg ...>...</svg>
                    </div>
                    <span className="text-xs mt-1.5 text-gray-700 dark:text-gray-300 font-medium">OfferSelect</span>
                  </a>
    
                  {/* 链接 2:城市对比工具 */}
                  <a href="https://citycompare.zippland.com/" ... >
                    <div className="..."> {/* 图标背景及动画效果 */} </div>
                    <span className="...">城市对比</span>
                  </a>
    
                  {/* 链接 3:SnapSolver,AI 自动笔试答题工具 */}
                  <a href="https://snapsolver.zippland.com/" ... >
                    <div className="..."> {/* 图标背景及动画效果 */} </div>
                    <span className="...">AI笔试</span>
                  </a>
    
                  {/* 链接 4:拼豆图纸生成工具 */}
                  <a href="https://perlerbeads.zippland.com/" ... >
                    <div className="..."> {/* 图标背景及动画效果 */} </div>
                    <span className="...">拼豆图纸</span>
                  </a>
                </div>
    
                {/* 页脚底部文字:版权或宣传 */}
                <div className="text-center">
                  <span className="text-[10px] text-gray-400 dark:text-gray-500">更多实用工具 by zippland.com</span>
                </div>
              </div>
            </footer>
          </body>
        </html>
      );
    }

🔄 修改后重新构建

  1. 每次修改完源码,确保你在项目根目录(worth-calculator)运行,这一步骤使用npm构建镜像需要的时间较长,耐心等待:

    docker compose up -d --build
  2. 查看正在运行的项目容器

    docker compose ps
    
    或
    
    docker-compose ps
  3. 查看正在运行的项目容器实时日志,按Ctrl+C中断查看。

    docker compose logs -f
  4. 确保改动生效。


🪟转换为静态文件

  1. 如果你不想使用Docker运行它,或者觉得构建成docker镜像太麻烦,可以把它转变为静态文件,使用群晖NAS的Web Station进行创建网页。

  2. 简单描述一下逻辑

  3. 在群晖的套件中心安装Node.js(测试安装v22版本)

  4. 创建一个文件夹,将你修改后的源码放进去,终端内进入该文件夹的路径。

  5. 执行命令安装npm(需要的时间较长,取决于你的网络质量。)

    npm install
  6. 执行命令,将源码转变成静态文件。

    ./node_modules/.bin/next build && ./node_modules/.bin/next export
  7. 查看并列出转变后的文件

    ls -l out/
  8. 转变之后当前文件夹内会多出三个文件夹,修改源码后重新构建不需要删除.next和node_modules文件夹(免得重新下载工具,浪费时间。)
    .nextnode_modules是构建工具的文件
    out是存放转变成静态文件的文件夹

  9. 打开群晖的Web Station,创建网页服务>静态网页>文件位置选择out文件夹。
    网络门户>设置指定的端口,然后访问即可>路由器端口映射,可建设网站。

  10. 如果你不会修改源码可以下载cursor软件,进行AI编程,你说它改。


相关地址


部署顺利,期待你用这个工具精确计算出自己的“打工值”!玩转工作价值计算!


文末

👇👇👇