You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React JS中如何实现薪资字段自动求和与输入框自动聚焦?

核心问题点
  • 净薪资计算逻辑失效:年薪输入框的onChange事件里错误将固定津贴、浮动津贴等4个字段统一赋值成年薪输入框的值,且这几个字段单独修改时没有触发净薪资重算;另外输入框默认返回字符串类型值,直接相加会触发字符串拼接而非数值求和
  • 自动聚焦逻辑不完整:仅给年薪输入框绑定了选中效果,没有在组件挂载阶段触发聚焦动作,无法实现加载后自动定位到输入框的效果
  • 表单绑定方式错误:所有输入项用defaultValue做单向绑定,state更新后输入框不会同步展示最新计算值
  • 提交逻辑冗余:提交按钮额外绑定的点击事件重复设置state无实际作用,也没有实现fetch提交的完整逻辑
修复后完整代码
import { useState, useRef, useEffect } from 'react';

export default function PayrollForm() {
  // 初始化薪资表单状态
  const [payroll, setPayroll] = useState({
    annual: '',
    monthly: 0,
    basic: 0,
    hra: 0,
    fixedAllowance: 0,
    variableAllowance: 0,
    employerShare: 0,
    statutoryBonus: 0,
    netSalary: 0
  });

  const annualInputRef = useRef(null);

  // 组件加载完成后自动聚焦年薪输入框
  useEffect(() => {
    annualInputRef.current?.focus();
  }, []);

  // 统一数值转换,避免字符串拼接问题
  const toNumber = (val) => Number(val) || 0;

  // 统一净薪资计算逻辑
  const calculateNetSalary = (formData) => {
    return Math.round(
      toNumber(formData.basic) +
      toNumber(formData.hra) +
      toNumber(formData.fixedAllowance) +
      toNumber(formData.variableAllowance) +
      toNumber(formData.employerShare) +
      toNumber(formData.statutoryBonus)
    );
  };

  // 年薪输入联动计算逻辑
  const onAnnualChange = (e) => {
    const annual = toNumber(e.target.value);
    const monthly = Math.round(annual / 12);
    const basic = Math.round((monthly / 100) * 50);
    const hra = Math.round((basic / 100) * 50);

    const updatedPayroll = {
      ...payroll,
      annual,
      monthly,
      basic,
      hra
    };
    updatedPayroll.netSalary = calculateNetSalary(updatedPayroll);
    setPayroll(updatedPayroll);
  };

  // 其他可编辑薪资项的统一变更处理
  const onAllowanceChange = (e, field) => {
    const value = toNumber(e.target.value);
    const updatedPayroll = {
      ...payroll,
      [field]: value
    };
    updatedPayroll.netSalary = calculateNetSalary(updatedPayroll);
    setPayroll(updatedPayroll);
  };

  // 表单提交逻辑
  const addPayroll = async (e) => {
    e.preventDefault();
    try {
      const res = await fetch('/api/payroll', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(payroll)
      });
      if (res.ok) {
        // 提交成功后可自定义重置表单、弹出提示等逻辑
        alert('薪资数据提交成功');
      }
    } catch (err) {
      console.error('提交失败:', err);
    }
  };

  return (
    <div className='payroll_main_container'>
      <form onSubmit={addPayroll}>
        <div className='form_group'>
          <label>Annual Salary</label>
          <input type='number' className='form-control' id='annual'
            value={payroll.annual}
            onChange={onAnnualChange}
            ref={annualInputRef}
            onFocus={() => {
              annualInputRef.current.select();
            }}
            placeholder='Annual' />
          <label>Monthly Salary</label>
          <input type='number' className='form-control' id='monthly'
            value={payroll.monthly}
            readOnly
            placeholder='Monthly' />
          <label>Basic Salary</label>
          <input type='number' className='form-control' id='basic'
            value={payroll.basic}
            readOnly
            placeholder='Basic' />
          <label>HRA</label>
          <input type='number' className='form-control' id='hra'
            value={payroll.hra}
            readOnly
            placeholder='HRA' />
          <label>Fixed Allowance</label>
          <input type='number' className='form-control' id='fixedAllowance'
            onChange={(e) => onAllowanceChange(e, 'fixedAllowance')}
            value={payroll.fixedAllowance}
            placeholder='Fixed Allowance' />
          <label>Variable Allowance</label>
          <input type='number' className='form-control' id='variableAllowance'
            onChange={(e) => onAllowanceChange(e, 'variableAllowance')}
            value={payroll.variableAllowance}
            placeholder='Variable Allowance' />
          <label>Employer Share</label>
          <input type='number' className='form-control' id='employerShare'
            onChange={(e) => onAllowanceChange(e, 'employerShare')}
            value={payroll.employerShare}
            placeholder='Employer Share' />
          <label>Statutory Bonus</label>
          <input type='number' className='form-control' id='statutoryBonus'
            onChange={(e) => onAllowanceChange(e, 'statutoryBonus')}
            value={payroll.statutoryBonus}
            placeholder='Statutory Bonus' />
          <label>Net Salary</label>
          <input type='number' className='form-control' id='netSalary'
            value={payroll.netSalary}
            readOnly
            placeholder='Net Salary' />

          <button type='submit' className='btn btn-primary'>Add</button>
        </div>
      </form>
    </div>
  )
}
关键修改说明
  • 所有输入项从非受控的defaultValue绑定改为value受控绑定,自动计算的月工资、基本工资、HRA、净薪资字段添加readOnly属性禁止手动修改,输入框类型统一设为number从输入层限制非数字内容
  • 抽离通用数值转换方法,所有输入值统一转为数字类型后再参与计算,从根源避免字符串拼接问题
  • 抽离独立的净薪资计算方法,年薪联动计算、津贴项修改时都会触发净薪资重算,所有关联项填写完成后净薪资会自动展示正确求和结果
  • 新增useEffect钩子在组件挂载完成后触发年薪输入框的聚焦动作,实现加载自动定位效果
  • 移除提交按钮上冗余的点击事件,在表单提交事件中实现完整的fetch POST提交逻辑,默认阻止表单原生跳转行为,按JSON格式序列化表单数据提交

内容的提问来源于stack exchange,提问作者Imtiyaz Sheikh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 14:18:28