SpringBoot+React實現(xiàn)計算個人所得稅
前言
在報表數(shù)據(jù)處理中,Excel公式擁有強大而多樣的功能,廣泛應用于各個業(yè)務領域。無論是投資收益計算、財務報表編制還是保險收益估算,Excel公式都扮演著不可或缺的角色。傳統(tǒng)的做法是直接依賴Excel來實現(xiàn)復雜的業(yè)務邏輯,并生成相應的Excel文件。因此只需在預設位置輸入相應參數(shù),Excel公式即可被激活,迅速計算并呈現(xiàn)結果。正因如此,在這類場景中,企業(yè)積累了大量用于計算的Excel文件,它們已經(jīng)成為了無價的財富。
然而,傳統(tǒng)的Excel文件方式存在難以管理和數(shù)據(jù)不安全的缺點。為了解決這些問題,可以采用B/S架構+Excel組件庫的方式。
本文將以個人所得稅的計算為例,使用React+Spring Boot+GcExcel來實現(xiàn)。首先準備好Excel文件,按照國家稅務總局提供的個稅計算頁面進行創(chuàng)建。
個人所得稅的收入類型有8種:
- 工資薪金所得
- 年終獎所得
- 勞務報酬所得
- 個體工商戶、生產(chǎn)經(jīng)營所得
- 酬勞所得
- 偶然所得
- 利息、股息、紅利所得
- 財產(chǎn)轉讓所得
其中,工資薪金所得最為復雜,包括社會保險和專項扣除。每種類型的計稅方式都不同,為了便于理解,我們?yōu)槊總€類型創(chuàng)建了一個工作表進行計算。
以下是準備好的Excel文件,其中藍色部分為需要輸入?yún)?shù)的單元格,其他單元格將自動計算。

完成準備工作后,下面開始前后端工程的搭建。
實踐
前端 React
創(chuàng)建React工程
新建一個文件夾,如TaxCalculator,進入文件夾,在資源管理器的地址欄里輸入cmd,然后回車,打開命令行窗口。使用下面的代碼創(chuàng)建名為client-app的react app。
npx create-react-app salary-client
進入剛創(chuàng)建的salary-client文件夾,使用IDE,比如VisualStudio Code打開文件夾。
界面部分
個人所得稅涉及的收入類型一共有8種,其中(“酬勞所得”,“偶然所得”,“利息、股息、紅利所得”,“財產(chǎn)轉讓所得”)四種的計算方式接近,UI布局相似,借助React的component特性,最終需要提供5種表單界面。
如下圖所示:

為了讓UI看起來更好看一些,可以先引入一個UI框架,這里我們使用了MUI。
npm install @mui/material @emotion/react @emotion/styled
首先,更新Src/App.js的代碼,其中添加了DarkMode的Theme, 代碼如下:
import './App.css';
import { ThemeProvider } from '@emotion/react';
import { createTheme } from '@mui/material';
import { FormContainer } from './Component/FormContainer';
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
});
function App() {
return (
<ThemeProvider theme={darkTheme}>
<div className="App-header">
<h2>個人所得稅計算器</h2>
<FormContainer></FormContainer>
</div>
</ThemeProvider>
);
}
export default App;可以看到,App.js中引用了FormContainer,下來添加 ./Component/FormContainer.js。
FormContainer主要是提供一個Selector,讓用戶選擇收入類型,根據(jù)選擇的類型渲染不同的組件。
import React, { useState } from 'react';
import { SalaryIncome } from "./SalaryIncome"
import { NativeSelect, FormControl } from '@mui/material';
import { BounsIncome } from './BounsIncome';
import { CommercialIncome } from './CommercialIncome';
import { LaborIncome } from './LaborIncome';
import { OtherIncome } from './OtherIncome';
export const FormContainer = () => {
const [calcType, setCalcType] = useState("工資薪金所得");
const GetIncomeControl = () => {
switch (calcType) {
case "工資薪金所得":
return <SalaryIncome calcType={calcType}></SalaryIncome>;
case "年終獎所得":
return <BounsIncome calcType={calcType}></BounsIncome>;
case "勞務報酬所得":
return <LaborIncome calcType={calcType}></LaborIncome>;
case "個體工商戶、生產(chǎn)經(jīng)營所得":
return <CommercialIncome calcType={calcType}></CommercialIncome>;
default:
return <OtherIncome calcType={calcType}></OtherIncome>;
}
}
return (
<div style={{ width: "60vw", marginTop: "5vh" }}>
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<NativeSelect labelId="demo-simple-select-label" id="demo-simple-select"
value={calcType} label="類型" onChange={e => setCalcType(e.target.value)} >
<option value="工資薪金所得">工資薪金所得</option>
<option value="年終獎所得">年終獎所得</option>
<option Item value="勞務報酬所得">勞務報酬所得</option>
<option value="個體工商戶、生產(chǎn)經(jīng)營所得">個體工商戶、生產(chǎn)經(jīng)營所得</option>
<option value="酬勞所得">酬勞所得</option>
<option value="偶然所得">偶然所得</option>
<option value="利息、股息、紅利所得">利息、股息、紅利所得</option>
</NativeSelect>
</FormControl>
{GetIncomeControl()}
</div>);
}例如:\<SalaryIncome calcType={calcType}\>\</SalaryIncome\>; 同時會將calcType傳遞進去。
接下來,分別創(chuàng)建幾個xxxIncome組件。
1.工資薪金所得 SalaryIncome.js
import React, { useState } from 'react';
import { TextField, Button, Stack } from '@mui/material';
import axios from 'axios';
export const SalaryIncome = (props) => {
const [income, setIncome] = useState("");
const [insurance, setInsurance] = useState("");
const [childEdu, setChildEdu] = useState("");
const [selfEdu, setSelfEdu] = useState("");
const [treatment, setTreatment] = useState("");
const [loans, setLoans] = useState("");
const [rent, setRent] = useState("");
const [elder, setElder] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [deduction, setDeduction] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {
event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
insurance: insurance,
childEdu: childEdu,
selfEdu: selfEdu,
treatment: treatment,
loans: loans,
rent: rent,
elder: elder,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {
event.preventDefault();
setIncome("");
setInsurance("");
setChildEdu("");
setSelfEdu("");
setTreatment("");
setLoans("");
setRent("");
setElder("");
setTaxableIncome("");
setTaxRate("");
setDeduction("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='primary'
label="稅前工資" onChange={e => setIncome(e.target.value)}
value={income} fullWidth required size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="社會保險/公積金" onChange={e => setInsurance(e.target.value)}
value={insurance} fullWidth size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary'
label="子女教育專項扣除" onChange={e => setChildEdu(e.target.value)}
value={childEdu} fullWidth size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="繼續(xù)教育專項扣除" onChange={e => setSelfEdu(e.target.value)}
value={selfEdu} fullWidth size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary'
label="大病醫(yī)療專項扣除" onChange={e => setTreatment(e.target.value)}
value={treatment} fullWidth size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="住房貸款利息專項扣除" onChange={e => setLoans(e.target.value)}
value={loans} fullWidth size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary'
label="住房租金專項扣除" onChange={e => setRent(e.target.value)}
value={rent} fullWidth size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="贍養(yǎng)老人專項扣除" onChange={e => setElder(e.target.value)}
value={elder} fullWidth size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary'
label="起征點" value="5000 元/月" fullWidth disabled size="small"/>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large">計算</Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large">重置</Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary'
label="應納稅所得額" value={taxableIncome} fullWidth disabled size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="稅率" value={taxRate} fullWidth disabled size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary'
label="速算扣除數(shù)" value={deduction} fullWidth disabled size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="應納稅額" value={tax} fullWidth disabled size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary'
label="稅后工資" value={takeHomeSalary} fullWidth disabled size="small"/>
</Stack>
</div>
)
}2.年終獎金所得 BounsIncome.js
import React, { useState } from 'react';
import { TextField, Button, Stack } from '@mui/material';
import axios from 'axios';
export const BounsIncome = (props) => {
const [income, setIncome] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [deduction, setDeduction] = useState("");
const [monthlyWage, setMonthlyWage] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {
event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setMonthlyWage(data.monthlyWage);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {
event.preventDefault();
setIncome("");
setTaxableIncome("");
setTaxRate("");
setDeduction("");
setMonthlyWage("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='primary' size="small"
label="稅前工資" onChange={e => setIncome(e.target.value)}
value={income} fullWidth required />
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large">計算</Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large">重置</Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="應納稅所得額" value={taxableIncome} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="稅率" value={taxRate} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="速算扣除數(shù)" value={deduction} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="平均每月工資" value={monthlyWage} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="應納稅額" value={tax} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="稅后工資" value={takeHomeSalary} fullWidth disabled />
</Stack>
</div>
)
}3.勞務報酬所得 LaborIncome.js
import React, { useState } from 'react';
import { TextField, Button, Stack } from '@mui/material';
import axios from 'axios';
export const LaborIncome = (props) => {
const [income, setIncome] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [deduction, setDeduction] = useState("");
const [nonTaxablePart, setNonTaxablePart] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {
event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setNonTaxablePart(data.nonTaxablePart);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {
event.preventDefault();
setIncome("");
setTaxableIncome("");
setTaxRate("");
setDeduction("");
setNonTaxablePart("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='primary' size="small"
label="稅前工資" onChange={e => setIncome(e.target.value)}
value={income} fullWidth required />
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large">計算</Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large">重置</Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="應納稅所得額" value={taxableIncome} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="稅率" value={taxRate} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="速算扣除數(shù)" value={deduction} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="減除費用" value={nonTaxablePart} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="應納稅額" value={tax} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="稅后工資" value={takeHomeSalary} fullWidth disabled />
</Stack>
</div>
)
}4.個體工商戶、生產(chǎn)經(jīng)營所得 CommercialIncome.js
import React, { useState } from 'react';
import { TextField, Button, Stack } from '@mui/material';
import axios from 'axios';
export const CommercialIncome = (props) => {
const [income, setIncome] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [deduction, setDeduction] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {
event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {
event.preventDefault();
setIncome("");
setTaxableIncome("");
setTaxRate("");
setDeduction("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='primary' size="small"
label="稅前工資" onChange={e => setIncome(e.target.value)}
value={income} fullWidth required />
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large">計算</Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large">重置</Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="應納稅所得額" value={taxableIncome} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="稅率" value={taxRate} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="速算扣除數(shù)" value={deduction} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="應納稅額" value={tax} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="稅后工資" value={takeHomeSalary} fullWidth disabled />
</Stack>
</div>
)
}5.余下四種類型 OtherIncome.js
import React, { useState } from 'react';
import { TextField, Button, Stack } from '@mui/material';
import axios from 'axios';
export const OtherIncome = (props) => {
const [income, setIncome] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {
event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {
event.preventDefault();
setIncome("");
setTaxableIncome("");
setTaxRate("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='primary' size="small"
label={props.calcType} onChange={e => setIncome(e.target.value)}
value={income} fullWidth required />
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large">計算</Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large">重置</Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="應納稅所得額" value={taxableIncome} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="稅率" value={taxRate} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{ marginBottom: 2 }}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="應納稅額" value={tax} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="稅后工資" value={takeHomeSalary} fullWidth disabled />
</Stack>
</div>
)
}此時,完成UI部分后,可以嘗試運行起來,效果如下:
//通過代碼運行React app npm start

可以試著填一些數(shù)據(jù),但是當我們點擊計算時會報錯,這是因為服務端還沒有準備好。
前端請求部分
熟悉Axios的同學可以跳過這部分,前面的代碼里,已經(jīng)給出了Axois發(fā)送請求的代碼。
可以看到無論是哪一種類型的組件,請求都發(fā)送到了相同的url("api/calcPersonTax"),以SalaryIncome為例,代碼如下:
async function calculateTax(event) {
event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
insurance: insurance,
childEdu: childEdu,
selfEdu: selfEdu,
treatment: treatment,
loans: loans,
rent: rent,
elder: elder,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}可以看到,整個請求變得非常簡單,主要是把state的值取出來,通過post請求發(fā)送到服務端,然后根據(jù)返回值,把數(shù)據(jù)重新設給state,這樣就完成UI數(shù)據(jù)的更新了。
配置請求轉發(fā)中間件
我們在請求時訪問的是相對地址,React本身有一個nodeJS,默認的端口是3000,而Spring Boot的默認端口是8080。前端直接訪問會有跨域的問題,因此我們要做一個代理的配置。
在src文件夾下面添加文件,名為setupProxy.js,代碼如下:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true,
})
);
};服務端 Spring Boot
創(chuàng)建工程及添加依賴
使用IDEA創(chuàng)建一個Spring Boot工程,如果使用的是社區(qū)(community)版本,不能直接創(chuàng)建Spring Boot項目,那可以先創(chuàng)建一個空項目,idea創(chuàng)建project的過程,就跳過了,這里我們以創(chuàng)建了一個gradle項目為例。
plugins {
id 'org.springframework.boot' version '3.0.0'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
id 'war'
}
group = 'org.example'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.grapecity.documents:gcexcel:6.2.0'
implementation 'javax.json:javax.json-api:1.1.4'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
test {
useJUnitPlatform()
}在dependencies 中,我們除了依賴Spring Boot之外,還添加了GcExcel的依賴,后面導出時會用到GcExcel,目前的版本是6.2.0。
添加API
在Application類上,添加屬性 @RequestMapping("/api").,并添加 calcPersonTax API。
@Spring BootApplication
@RestController
@RequestMapping("/api")
public class SalaryTaxCalculator {
public static void main(String[] args) {
SpringApplication.run(SalaryTaxCalculator.class, args);
}
@PostMapping("/calcPersonTax")
public CalcResult calcTax(@RequestBody CalcParameter par) {
Workbook workbook = new Workbook();
workbook.open(GetResourcePath());
return CalcInternal(workbook, par);
}
private String GetResourcePath(){
return Objects.requireNonNull(SalaryTaxCalculator.class.getClassLoader().getResource("PersonalTaxCalcEngine.xlsx")).getPath();
}
private CalcResult CalcInternal(Workbook workbook, CalcParameter par) {
//todo
}
}可以看到在CalcInternal方法內(nèi),我們使用GcExcel,根據(jù)calcType來判斷使用哪一個sheet來進行計算。對不同Sheet只需要通過GcExcel設值,并從特定的格子里取值即可。
同時,我們還需要創(chuàng)建兩個類,CalcParameter和CalcResult。CalcParameter用于從request中把post的data解析出來,CalcResult用于在response中返回的數(shù)據(jù)。
CalcParameter:
public class CalcParameter {
public String calcType;
public double income;
public double insurance;
public double childEdu;
public double selfEdu;
public double treatment;
public double loans;
public double rent;
public double elder;
}CalcResult:
public class CalcResult {
public double taxableIncome;
public double taxRate;
public double deduction;
public double tax;
public double takeHomeSalary;
public double monthlyWage;
public double nonTaxablePart;
}使用GcExcel完成公式計算
前面我們定義了 CalcInternal,在 CalcInternal 中,我們需要使用GcExcel來完成公式計算。
GcExcel的公式計算是自動完成的,我們使用workbook打開Excel文件后,只需要set相關的value。之后在取值時,GcExcel會自動計算響應公式的值。
private CalcResult CalcInternal(Workbook workbook, CalcParameter par) {
var result = new CalcResult();
var sheet = workbook.getWorksheets().get(par.calcType);
switch (par.calcType) {
case "工資薪金所得" -> {
sheet.getRange("B1").setValue(par.income);
sheet.getRange("D1").setValue(par.insurance);
sheet.getRange("B2").setValue(par.childEdu);
sheet.getRange("D2").setValue(par.selfEdu);
sheet.getRange("B3").setValue(par.treatment);
sheet.getRange("D3").setValue(par.loans);
sheet.getRange("B4").setValue(par.rent);
sheet.getRange("D4").setValue(par.elder);
result.taxableIncome = (double) sheet.getRange("B9").getValue();
result.taxRate = (double) sheet.getRange("D9").getValue();
result.deduction = (double) sheet.getRange("B10").getValue();
result.tax = (double) sheet.getRange("D10").getValue();
result.takeHomeSalary = (double) sheet.getRange("B11").getValue();
}
case "年終獎所得" -> {
sheet.getRange("B1").setValue(par.income);
result.taxableIncome = (double) sheet.getRange("B3").getValue();
result.taxRate = (double) sheet.getRange("D3").getValue();
result.deduction = (double) sheet.getRange("B4").getValue();
result.monthlyWage = (double) sheet.getRange("D4").getValue();
result.tax = (double) sheet.getRange("B5").getValue();
result.takeHomeSalary = (double) sheet.getRange("D5").getValue();
}
case "勞務報酬所得" -> {
sheet.getRange("B1").setValue(par.income);
result.taxableIncome = (double) sheet.getRange("B3").getValue();
result.taxRate = (double) sheet.getRange("D3").getValue();
result.deduction = (double) sheet.getRange("B4").getValue();
result.nonTaxablePart = (double) sheet.getRange("D4").getValue();
result.tax = (double) sheet.getRange("B5").getValue();
result.takeHomeSalary = (double) sheet.getRange("D5").getValue();
}
case "個體工商戶、生產(chǎn)經(jīng)營所得" -> {
sheet.getRange("B1").setValue(par.income);
result.taxableIncome = (double) sheet.getRange("B3").getValue();
result.taxRate = (double) sheet.getRange("D3").getValue();
result.deduction = (double) sheet.getRange("B4").getValue();
result.tax = (double) sheet.getRange("D4").getValue();
result.takeHomeSalary = (double) sheet.getRange("B5").getValue();
}
default -> {
sheet.getRange("B1").setValue(par.income);
result.taxableIncome = (double) sheet.getRange("B3").getValue();
result.taxRate = (double) sheet.getRange("D3").getValue();
result.tax = (double) sheet.getRange("B4").getValue();
result.takeHomeSalary = (double) sheet.getRange("D4").getValue();
}
}
return result;
}這樣就完成了服務端的代碼。
最終效果
我們可以使用工資薪金所得試驗一下,可以看到數(shù)據(jù)被計算出來了。因為目的是為了分享服務端公式計算的方案,所以計算的結果是否正確,就不做細致考慮。

總結
個稅計算的場景并不復雜,主要是通過Excel完成公式計算即可,在服務端使用GcExcel可以大幅度降低前后端的開發(fā)難度,系統(tǒng)的搭建過程可以完全不需要考慮計算的邏輯。
在實際的公式計算場景中,可能往往會比個稅計算的場景復雜,借助GcExcel這樣Excel組件庫,可以很容易的把已有的Excel文件遷移到線上,提高工作效率。
另外,本文中分享的代碼并不是最符合實際工作中的要求,讀者還可以從以下角度去優(yōu)化自己的代碼。
- 收入類型可以抽成枚舉,這樣維護和使用起來更容易。
- 目前每一個react組件里的冗余度還不低,還可以繼續(xù)抽象組件,避免重復寫代碼。
- 在服務端,因為公式計算的邏輯是不會變的,在實際場景中,也有可能同一時間要加載復數(shù)個Excel文件,可以考慮把workbook常駐內(nèi)存,來提高性能。
以上就是SpringBoot+React實現(xiàn)計算個人所得稅的詳細內(nèi)容,更多關于SpringBoot Reac計算個人所得稅的資料請關注腳本之家其它相關文章!
相關文章
Spring如何通過@Lazy注解解決構造方法循環(huán)依賴問題
循環(huán)依賴其實就是循環(huán)引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環(huán),這篇文章主要給大家介紹了關于Spring如何通過@Lazy注解解決構造方法循環(huán)依賴問題的相關資料,需要的朋友可以參考下2023-03-03
java進制轉換工具類實現(xiàn)減少參數(shù)長度
這篇文章主要為大家介紹了java進制轉換工具類實現(xiàn)減少參數(shù)長度示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02
使用Apache Ignite實現(xiàn)Java數(shù)據(jù)網(wǎng)格
今天我們來探討如何使用Apache Ignite來實現(xiàn)Java數(shù)據(jù)網(wǎng)格,Apache Ignite是一個高性能的內(nèi)存計算平臺,它提供了分布式緩存、數(shù)據(jù)網(wǎng)格和計算功能,可以顯著提高大規(guī)模應用的數(shù)據(jù)處理性能,感興趣的小伙伴跟著小編一起來看看吧2024-08-08

