
简介这是一个以网页开发技术构建的全屏个人所得税计算器源码包面向前端初学者和有报税测算需求的个人用户通过浏览器输入工资、五险一金及专项附加扣除项目即可自动计算应纳税总额并展示最终税额。资源包内共5个文件核心是单个超文本页面其余为4个网址快捷方式压缩后仅9KB无需配置环境解压即用。目前已有506人学习下载。源码重点展示了累进税率的分段计算逻辑借助脚本语言的条件判断处理不同收入档位同时支持子女教育、继续教育、住房贷款利息等多类专项附加扣除的录入与校验页面采用全屏自适应布局还包含正则表达式校验和用户输入提示适合作为前端表单交互与计税规则学习的实战参考。1. 一个 HTML 全屏个人所得税计算器源码包先搞懂它到底解决什么问题行政、财务、人事在月底最头疼的一件事是把几十个人的工资跑一遍个税。2019 年切换成累计预扣法之后「拿税后反推税前」「看本月该扣多少」这类问题就绕不开一个逐月累计的过程。标题里的「HTML全屏个人所得税计算器源码.zip」本质上是一个纯静态网页项目解压即用、双击打开、全屏展示不依赖 Node、不依赖后端接口、不需要数据库。它能解决的问题很具体在本地离线环境里用一个表单把每月收入、五险一金、专项附加扣除一股脑填进去直接得出当月应预扣税额和税后收入。这类源码包在网上下载站很常见但拿到手之后大多数人会遇到三个坎全屏适配在不同浏览器里表现不一致个税计算逻辑如果只是简单套月度税率表1 月和 12 月的数字就对不上zip 里如果还套着中文目录名解压后路径乱码导致 CSS 加载不出来。这篇文章就顺着这三条线把「解压、看懂、改对、跑通」的完整路径走一遍。适合的人群是前端开发者、薪资核算人员以及任何想快速做一个内部小工具但不想引入构建系统的工程师。下面先从全屏这个最容易被忽略的细节讲起。2. 先让页面「撑满」屏幕全屏适配的 HTML 骨架与 zip 检查2.1 全屏适配的三种写法以及为什么默认选100vh全屏这个需求听起来简单实际上前端圈子为它吵了好多年。常见的实现方式有三种height: 100vh、position: fixed配合inset: 0、以及min-height: 100%搭配 html/body 的显式高度。绝大多数源码包里的实现是第一种因为一行 CSS 就能让容器铺满视口。html, body { margin: 0; padding: 0; height: 100%; } #app { min-height: 100vh; display: flex; flex-direction: column; }这段样式有两个细节值得注意。min-height: 100vh而不是height: 100vh是为了避免内容超出视口时出现内部滚动条而不是页面滚动这在表单类工具里尤其关键因为个税计算器在手机上很可能要滚动。flex-direction: column则是为了让底部的「计算结果」区域始终贴在内容流的最下方而不是悬浮在视口底部后者在软键盘弹出时会挡住提交按钮。提示移动端 Safari 的100vh实际值是「视口高度减掉地址栏」所以100vh在 iPhone 上会漏出一截。更可靠的做法是同时设置min-height: 100dvh动态视口单位浏览器不支持时自动回退到100vh。2.2 一份没有构建工具的 HTML 骨架DOCTYPE、字符集与表单结构下载站里这类源码最常见的形态是一个index.html内联全部 CSS 和 JS因为作者要保证「双击就能跑」。连外链link和script src都怕用户解压后改了目录结构导致路径失效。我见过很多版本把样式和脚本全塞进一个文件这虽然不优雅但对供应链最短的离线工具来说反而是最可靠的。!doctype html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1, viewport-fitcover title个人所得税计算器/title style /* 全屏容器样式 */ body { margin: 0; font-family: system-ui, -apple-system, PingFang SC, Microsoft YaHei, sans-serif; } .calc-container { max-width: 480px; margin: 0 auto; padding: 16px; } .result-panel { background: #f5f7fa; border-radius: 8px; padding: 16px; margin-top: 16px; } .salary-input { width: 100%; padding: 12px; border: 1px solid #d0d7de; border-radius: 6px; box-sizing: border-box; } /style /head body main classcalc-container h1个人所得税计算器/h1 form idtax-form label formonth当前月份/label input typenumber idmonth min1 max12 value1 classsalary-input label forincome本月累计收入元/label input typenumber idincome value15000 classsalary-input label forinsurance本月三险一金个人缴纳元/label input typenumber idinsurance value2000 classsalary-input button typesubmit计算/button /form section classresult-panel idresult/section /main /body /html这里有几个参数不是随便写的。viewport-fitcover是给 iPhone 刘海屏用的没有这行横屏或使用 PWA 全屏模式时内容会被圆角裁掉。system-ui字体栈是「全平台不引入字体文件」最稳妥的选择比写成PingFang SC, Microsoft YaHei更省心因为 Windows 上没有 PingFang 时会自动降级到默认字体。max-width: 480px不是限制桌面显示而是让键盘输入时不会因为行宽拉太长导致眼睛盯不住当前行。2.3 zip 拿到手先别双击解压先看清单再查编码从下载站拿到的源码包第一步不是双击解压而是先看压缩包里到底有什么。Windows 自带的解压工具对中文文件名处理得不干净解压出来一堆乱码目录HTML 倒是能打开但同目录下的 CSS、JS 全部 404页面变成裸 HTML。# 用命令行工具先列出 zip 内容避免 Windows 资源管理器自动解压的坑 unzip -l 全屏个人所得税计算器源码.zip # 如果 zip 里是嵌套目录用 -d 指定解压目录避免覆盖当前文件 unzip -o 全屏个人所得税计算器源码.zip -d tax-calculatorunzip -l会在解压前打印包内文件清单重点看有没有__MACOSX、.DS_Store这类垃圾目录以及是否有中文嵌套目录。-d tax-calculator是建议的用法强制把所有文件解压到独立目录里防止压缩包根目录是一堆散文件的情况。如果解压后 HTML 打开正常但样式全没了F12打开控制台优先检查网络面板里 CSS 的路径九成是目录层级不对。2.4 三种「全屏」的区别浏览器 F11、PWA 与页面内全屏源码标题既然写了「全屏」那就要搞清楚它到底是哪种全屏。这里有个表格可以对照看因为很多人会混淆。全屏方式用户触发地址栏适用场景实现成本浏览器全屏F11手动按键隐藏固定办公电脑、单机展示零成本按 F11 进入全屏退出保留状态手动按键显示时短暂临时给同事看零成本Web APIrequestFullscreen()页面按钮浏览器 UI 被隐藏自助查询机、一体机需要加一段 JSPWA 独立窗口display: standalone安装应用隐藏手机主屏入口需要 manifest.json大多数个税源码包只是普通 HTML没有 manifest所以「全屏」实际指的是 F11 或页面自身撑满视口。如果你想做成一按就进去全屏的自助查询机就得用requestFullscreen()document.getElementById(fullscreen-btn).addEventListener(click, () { document.documentElement.requestFullscreen(); });注意requestFullscreen()返回的是一个 Promise在 Chrome 里如果调用时不在用户手势栈里会被拒绝所以必须放在 click 回调里直接同步调用不能在setTimeout里包一层。另外全屏只对「已加载的文档」生效iframe 场景下必须给 iframe 加allowfullscreen属性否则 API 会静默失效。3. 个税计算器计算逻辑累计预扣法在 JavaScript 里的正确实现3.1 为什么 1 月份算出来的税和 12 月份对不上很多新手写个税计算器上来就是「当月应纳税所得额 × 税率 - 速算扣除数」套用月度税率表直接出结果。这套逻辑在 2018 年 10 月之前是对的但现行个税采用累计预扣法后每月应预扣税额是「截至本月的累计应纳税额」减去「之前各月已预缴税额」的差额。翻译成人话1 月收入低可能不缴税5 月收入相同就可能开始补缴因为累计额跨过了税率级距的临界点。// 月度税率的错误写法只算当月结果每月都一样 function wrongMonthlyTax(salary, insurance) { const taxable salary - insurance - 5000; return taxable 0 ? taxable * 0.1 - 210 : 0; // 套用老税率表典型错误 }上面这段代码代表了很多「老旧源码包」的实际水平每月算出恒定税额看起来「稳定」但在年初少缴年末补缴的累计制下是错的。正确的做法是每次计算时都从 1 月推演到当前月把每月的收入、扣除额累加起来算出「截至本月的累计应预扣税额」再减掉「之前已扣的累计数」剩下的才是本月应该从工资里扣的金额。3.2 七级超额累进税率表把它翻译成常量数组现行综合所得预扣率表打满七档从 3% 到 45%每一档对应一个「上限」和「速算扣除数」。源码里最常见的坑是速算扣除数写错一位或者把「不超过 36000 元」的档位上界写成 36000.99 这种多余的小数位导致临界点多扣一分钱。正确的常量表应该是这样的const TAX_BRACKETS [ { upper: 36000, rate: 0.03, quickDeduction: 0 }, { upper: 144000, rate: 0.10, quickDeduction: 2520 }, { upper: 300000, rate: 0.20, quickDeduction: 16920 }, { upper: 420000, rate: 0.25, quickDeduction: 31920 }, { upper: 660000, rate: 0.30, quickDeduction: 52920 }, { upper: 960000, rate: 0.35, quickDeduction: 85920 }, { upper: Infinity, rate: 0.45, quickDeduction: 181920 } ];upper表示该档应纳税所得额的上限超过就落入下一档最后一档用Infinity收口避免写一个巨大的数字导致未来政策调整时忘记改。quickDeduction是速算扣除数它的作用是让你不用分段求每一档的税额直接「应纳税所得额 × 税率 - 速算扣除数」一步出结果。这套常量写死之后找税率的函数就是线性扫描function getRateInfo(cumulativeTaxable) { for (const bracket of TAX_BRACKETS) { if (cumulativeTaxable bracket.upper) { return bracket; } } }3.3 核心函数传入 1 到 12 月的数据输出每月应预扣税额一个能用于薪资核算的计算器不应该只算单月而应该接收一个包含全年度数据的数组从第 1 个月开始逐月累计。下面这个函数是源码包中最值得改写的部分配合数组数据才能应付「4 月补发工资」「9 月调整社保基数」这类真实场景。function calculateFullYearTax(monthlyData) { let cumulativeTaxable 0; // 累计应纳税所得额 let cumulativeTax 0; // 累计已预扣税额 const results []; for (let month 0; month monthlyData.length; month) { const { income, insurance, specialDeduction } monthlyData[month]; // 累计预扣应纳税所得额 累计收入 - 累计免税收入 - 累计减除费用 // 累计减除费用 5000 * 月份数 cumulativeTaxable income - insurance - 5000 - (specialDeduction || 0); // 应纳税额 累计应纳税所得额 * 税率 - 速算扣除数 - 累计已缴 const bracket getRateInfo(Math.max(cumulativeTaxable, 0)); const currentCumulativeTax Math.max(cumulativeTaxable, 0) * bracket.rate - bracket.quickDeduction; const monthTax Math.max(currentCumulativeTax - cumulativeTax, 0); results.push({ month: month 1, monthTax: Math.round(monthTax * 100) / 100, cumulativeTax: Math.round(currentCumulativeTax * 100) / 100 }); cumulativeTax currentCumulativeTax; } return results; } // 调用示例1-3 月每月都是 15000 收入、2000 社保、1000 专项附加扣除 const demo [ { income: 15000, insurance: 2000, specialDeduction: 1000 }, { income: 15000, insurance: 2000, specialDeduction: 1000 }, { income: 16000, insurance: 2100, specialDeduction: 1000 } ]; console.log(calculateFullYearTax(demo));逻辑说明每次循环先把本月的收入、三险一金、专项附加扣除累加进cumulativeTaxable注意这里减掉的 5000 是「本月减除费用」因为循环是逐月累加的所以乘数就是月份数本身不需要额外维护计数器。Math.max(cumulativeTaxable, 0)是为了防止负数否则在 1 月收入低于扣除额时getRateInfo会错误地命中第一档但cumulativeTax变成负数导致下一个月的计算结果异常。月末把cumulativeTax更新为currentCumulativeTax下个月计算「本月应缴」时才能减掉已经缴过的部分。提示这个函数里没有做「累计应扣额低于已扣额时允许退税」的处理。累计预扣法在年度中间若收入骤降currentCumulativeTax - cumulativeTax可能为负数但单位发工资时通常不会在当月「退税」只会在年度汇算清缴时统一退。这里用Math.max(..., 0)处理是符合实务习惯的。4. 拿到 zip 源码之后怎么改从「能算」到「算得对」4.1 两种运行方式以及你最好避开的那一种解压完成不要直接双击index.html就开始改。file://协议下浏览器对本地文件的安全限制会导致fetch(data.json)直接失败而且 localStorage 在各目录下行为不一致。个税计算器如果要把多个月的工资数据存进浏览器file://下可能换个目录数据就没了。更可靠的运行方式是用本地静态服务器cd tax-calculator python3 -m http.server 8080 # 然后浏览器打开 http://localhost:8080不用纠结用不用框架纯 HTML 源码最配的就是这种零配置服务器。http.server是 Python 自带的静态文件服务不需要额外安装依赖。如果你机器上有 Node也可以用npx serve .替代它们的效果一样。最不推荐的方式是「改完 HTML 直接拖进微信发送再预览」微信内置浏览器的兼容性对100vh和requestFullscreen()的支持都不可靠排查起来还看不见控制台。4.2 把专项附加扣除改成可配置项新增三个输入框的标准套路很多源码包里只有「收入」和「三险一金」两个输入框专项附加扣除是写死在代码里的一个常量。这对多数人不适用因为赡养老人、房贷利息、住房租金这三项金额因人而异。改成可配置项并不复杂在 HTML 表单里加一个选择框然后在计算时把它读进specialDeduction字段。label forspecial-type专项附加扣除类型/label select idspecial-type classsalary-input option value0无/option option value1000子女教育1000元/月/option option value2000赡养老人2000元/月独生子女/option option value1500住房租金1500元/月直辖市/省会/option option value1000住房贷款利息1000元/月/option /selectdocument.getElementById(tax-form).addEventListener(submit, (event) { event.preventDefault(); const income parseFloat(document.getElementById(income).value); const insurance parseFloat(document.getElementById(insurance).value); const specialDeduction parseFloat(document.getElementById(special-type).value); // 把 singleMonth 替换成上一节的全年度累计函数 const result calculateFullYearTax([{ income, insurance, specialDeduction }]); renderResult(result); });这段代码里有个关键点parseFloat(document.getElementById(special-type).value)因为option的value直接是数字字符串parseFloat能安全转换。不要用parseInt它会在读到1000时没问题但如果以后有人把值改成0或行为就不一致了。另外当你只填了「当前月份」和「本月数据」时上面我用长度为 1 的数组调用calculateFullYearTax只会从当前月当第 1 个月算正确做法应该是构造从 1 月到当前月的数组前面的月份用已有历史数据填充这个逻辑要看你源码包里的表单设计但核心函数是不用动的。4.3 加入年终奖单独计税切换一个开关两种算法源码包的另一个常见短板是不支持年终奖单独计税。虽然年终奖单独计税的政策延续性需要关注但作为计算器工具把两种算法都放出来让使用者自己对比是合理的。function calcBonusTax(bonus) { // 年终奖总额除以 12 确定税率用月换算后的税率档位 const monthly bonus / 12; const bracket getRateInfo(monthly); // 注意年终奖单独计税的速算扣除数与综合所得不同政策差异已按经典口径处理 const tax bonus * bracket.rate - bracket.quickDeduction / 12 * 12; return Math.max(tax, 0); }这里有个容易踩的坑年终奖税率表用的是「按月换算后的综合所得税率表」它的速算扣除数在计算年终奖时并不是直接乘 12而是直接按月表查到的税率乘以年终奖再减去月表速算扣除数的一次性扣除。也就是说quickDeduction在这个场景下不能直接套用综合所得的表值需要另配一张「月度税率表」。上面代码中bracket.quickDeduction / 12 * 12这种写法虽然数值上不变但在语义上是混乱的正确的做法是单独定义一张BONUS_BRACKETS表以免后续调整综合所得表时连着年终奖一起错掉。5. 验证与发布用手工推演检查源码最后落到一个可复现的方案5.1 手工推演一组真实数字给代码一个「对照答案」改完代码先别急着上线拿一组具体数字手算一遍验算你的累计逻辑。假设月薪 15000三险一金 2000独生子女赡养老人 2000那么每月专项附加扣除合计 2000 元每月应纳税所得额累计增加15000 - 2000 - 5000 - 2000 6000元。月份累计应纳税所得额累计应预扣税额结算当月实缴差额1 月60001806000×3%落在第一档1802 月120003601803 月180005401804 月240007201805 月300009001806 月3600010801807 月42000168042000×10%-2520600看到 7 月的分水岭了吗1 到 6 月每月都是 180 元6 月刚好卡在 36000 的临界点上7 月累计超过 36000累计税额从 1080 跳变到 1680当月应缴因此从 180 变成 600 元。这就是「累计预扣法下 7 月个税突然翻倍」的原因。用这张表去对源码的输出如果数值对不上九成是税率表常量或累计变量初始化有问题。5.2 把函数挂到全局对象上直接在控制台跑断言调试时最烦的是每次改完都要在页面上填一遍表单。把核心函数挂到window上就能在浏览器控制台直接调用用console.assert判断结果是否符合预期。// 在源码的末尾追加这行把函数暴露到全局 window.__taxCalculator { calculateFullYearTax, TAX_BRACKETS }; // 控制台里执行断言验证 1~7 月的累计结果 const case1 Array.from({ length: 7 }, () ({ income: 15000, insurance: 2000, specialDeduction: 2000 })); const output window.__taxCalculator.calculateFullYearTax(case1); console.assert(output[5].cumulativeTax 1080, 6 月累计应扣应为 1080实际是, output[5].cumulativeTax); console.assert(output[6].monthTax 600, 7 月当月应扣应为 600实际是, output[6].monthTax);断言失败时会直接打印第二个参数你甚至不用开 Sources 面板打断点。这套验证方法对有表单校验的复杂场景特别有用你不需要反复点击页面只要在控制台构造不同的数组瞬间跑完一整年的情况。上面第 6 行使用Array.from({ length: 7 }, () ({...}))生成了 7 个月完全相同的数据如果你要测「9 月补齐社保基数」这类单月变化也只需要把对应下标的数据替换掉。注意window.__taxCalculator这种命名约定是给调试用的不要带着上生产环境否则任何访客都能在控制台里调用你的计算函数虽然个税计算器没有敏感数据但这是个坏习惯。5.3 从 zip 到线上最小改动发布方案如果这个计算器不只是自己用还要发给别人用最省事的路径不是打包成 zip 再传文件而是把整个目录丢上任何一个静态托管。index.html内联了样式和脚本天然适合静态托管。常见的做法是传到 GitHub 仓库并开起 GitHub Pages或者在服务器上配一个 Nginx 静态目录# 服务器上把解压后的目录放到 web 根目录 sudo cp -r tax-calculator /var/www/html/ # 重启 nginx 加载静态文件配置 sudo systemctl reload nginxcp -r拷贝时要注意目录权限Nginx 的 worker 进程通常以www-data用户运行目录至少要有读和执行权限。如果页面打开是白屏sudo tail -f /var/log/nginx/error.log看权限报错。发布之后记得用「无痕窗口」访问一次避免本地缓存的旧 JS 干扰判断。最后检查一遍地址栏协议是不是https因为requestFullscreen()在部分浏览器的file://或非安全上下文里无法调用这个坑在本地能跑通、线上白屏时最容易让人摸不着头脑。本文还有配套的精品资源点击获取