这个作业属于哪个课程 | <https://bbs.csdn.net/forums/ssynkqtd-05?typeId=5171424> |
---|---|
这个作业要求在哪里 | <https://bbs.csdn.net/topics/617377308> |
这个作业的目标 | 前后端分离,实现基础计算器和利率计算器 |
其他参考文献 |
目录
一、git仓库链接和代码规范链接
102101101杨婧怡 / 前端 · GitCode
102101101杨婧怡 / 后端 · GitCode
https://www.cnblogs.com/LyndonMario/p/8149876.html
二、PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 40 |
• Estimate | • 估计这个任务需要多少时间 | 30 | 40 |
Development | 开发 | 1360 | 1760 |
• Analysis | • 需求分析 (包括学习新技术) | 800 | 850 |
• Design Spec | • 生成设计文档 | 40 | 50 |
• Design Review | • 设计复审 | 40 | 50 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
• Design | • 具体设计 | 80 | 100 |
• Coding | • 具体编码 | 400 | 500 |
• Code Review | • 代码复审 | 60 | 60 |
• Test | • 测试(自我测试,修改代码,提交修改) | 100 | 130 |
Reporting | 报告 | 105 | 110 |
• Test Repor | • 测试报告 | 60 | 60 |
• Size Measurement | • 计算工作量 | 15 | 20 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1495 | 1910 |
三、成品展示
1.基础计算器
- 功能1:加减乘除、取余、括号运算
- 功能2:清零回退
- 实现字符串输入的清零和回退功能
- 功能3:错误提示
异常抛出
- 功能4:读取历史记录
- 能用ans按钮返回上一个计算结果,并且查看历史记录读取最后一个字符串式子和对应的计算结果
2.利率计算器
利率计算器做的有问题(显示利率的小数点错了,还有目前只能算最上面的那个选项......可惜这周时间来不及改)
- 存款利率
- 贷款利率
四、设计实现过程
1.前端使用vue,展示页面并将运算结果返回到后端,查询历史记录时从后端读取。
若计算存贷款利率则从后端获取利率。
2.数据库中新建存放历史记录、存贷款利率的表。
3.后端使用了springboot和mybatis,将运算结果存到历史记录,接收到前端查询请求时从数据库中查询。
五、代码说明
前端的计算器页面
<template>
<div class="computeds-container">
<input class="input" :value="value" disabled>
<div class="click-btns">
<button v-for="(item, i) in btnArr" :key="i" @click="clickBtnFn(i, item)" class="btn"
:class="{ 'btn0': i === 28 }">{{ item }}</button>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="history" label="历史记录" width="200">
</el-table-column>
</el-table>
</div>
<!--展示历史记录
-->
</template>
<script>
// import axios from 'axios'
import requests from "../utils/request";
export default {
name: 'ComputedS',
data() {
return {
btnArr: [
'(', ')', '%', '7', '8', '9', '/',
'AC', 'sin', 'cos', '4', '5', '6', '*',
'Back', 'tan', 'lg', 1, '2', '3', '-',
'Ans', '^', '√', '.', '0', '=', '+',
],
value: '',
//historyShow: false
tableData: [{ history: '' }]
}
},
methods: {
clickBtnFn(index, item) {
this.value = this.value + ''
//if (this.value === '') return
//if (item === 'Ans') {//历史记录
// this.historyShow = !this.historyShow
//}
if (item === 'AC') {
this.value = ''
} else if (item === 'Back') {
this.value = this.value.slice(0, this.value.length - 1)
}
else if (item === '=') {
// this.value += item
const exp = this.value;
requests.get('/api/calculator?exp=' + exp).then(res => {
console.log(res)
if (res.data.status == 500) {
this.$message({
message: res.data.message,
type: 'error'
});
}
this.value = res.data
})
} else if (item == 'Ans') {
requests.get('/api/history').then(res => {
console.log(res.data)
if (res.status == 200) {
this.tableData = res.data.map((ele) => {
return {
history: ele.expression + ele.result
}
})
}
})
}
else {
this.value += item
}
}
}
}
</script>
<style lang="less" scoped>
.computeds-container {
max-width: 550px;
padding: 0 20px;
background-color: #fafafa;
border-radius: 20px;
box-sizing: border-box;
.input {
width: 100%;
height: 40px;
padding: 0 20px;
margin-bottom: 10px;
border: 1px solid #b3d7dd;
border-radius: 20px;
text-align: right;
font-size: 20px;
line-height: 42px;
box-sizing: border-box;
}
.click-btns {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.btn {
//width=
flex: 10%;
padding: 12px 0px;
margin: 10px;
background-color: #b3d7dd;
border: 0;
border-radius: 18px;
box-shadow: 7px 7px 7px #aaa;
font-size: 20px;
cursor: pointer;
}
.btn0 {
flex: 40%;
}
}
}
</style>
前端的利率计算器页面
<template>
<div>
<div>
<span>利率计算器</span>
</div>
<el-row :gutter="20">
<el-col :span="6">
<el-input placeholder="请输入内容" v-model="inputRate" clearable>
</el-input>
</el-col>
<!-- 利率类型展示 -->
<el-col :span="6">
<el-select v-model="value" clearable @change="getDuration()" placeholder="请选择利率类型">
<el-option v-for="item in rateOptions" :key="item.id" :label="item.rateType" :value=item.rateType>
</el-option>
</el-select>
</el-col>
<!-- 周期利率下拉展示 -->
<el-col :span="6">
<el-select v-model="durationValue" clearable :disabled="selectDisabled" placeholder="请选择利率周期">
<el-option v-for="item in durationOptions" :key="item.id" :label="item.durationRate" :value="item.id">
</el-option>
</el-select>
</el-col>
<el-input v-model="result" disabled placeholder="利率结果"></el-input>
</el-row>
<el-button type="primary" plain @click="calculator()">计算利率</el-button>
</div>
</template>
<script>
import requests from "../utils/request";
export default {
created() {
requests.get('/api/rateType').then(res => {
this.rateOptions = res.data
})
},
data() {
return {
inputRate: '',
rateOptions: [],
value: '',
selectDisabled: true,
durationValue: '',
durationOptions: [{
id: 0,
durationRate: '全部',
duration: 0
}],
result: ''
}
},
methods: {
alertMessage() {
if (this.value === '') {
this.$message({
message: '计算利率类型不能为空',
type: 'error'
});
} else if (this.inputRate < 0) {
this.$message({
message: this.value + '不能为负数',
type: 'error'
});
} else if (this.inputRate === '') {
this.$message({
message: '利率值不能为空',
type: 'error'
});
}
},
getDuration() {
if (this.value === '存款利率') {
requests.get("/api/queryDeposit").then(res => {
this.durationOptions = res.data.map((ele) => {
var date = '年';
if (ele.duration == 0.3 || ele.duration == 0.6) {
ele.duration = ele.duration * 10;
date = '月'
}
return {
durationRate: ele.isDemand ? '活期存款' + ele.duration + date + " -" + (parseFloat(ele.rate) * 10).toFixed(2) + "%" : '定期存款' + ele.duration + date + " -" + (parseFloat(ele.rate) * 10).toFixed(2) + "%",
id: ele.id,
duration: ele.duration
}
})
})
this.selectDisabled = false;
} else if (this.value === '贷款利率') {
requests.get("/api/queryLoan").then(res => {
this.durationOptions = res.data.map((ele) => {
var date = '年';
if (ele.duration == 0.6) {
ele.duration = ele.duration * 10;
date = '个月'
} else if (ele.duration > 1 && ele.duration < 3) {
ele.duration = '1至3'
} else if (ele.duration > 3 && ele.duration < 5) {
ele.duration = '3至5'
}
return {
durationRate: ele.duration + date + " -" + (parseFloat(ele.rate) * 10).toFixed(2) + "%",
id: ele.id,
duration: ele.duration
}
})
})
this.selectDisabled = false;
}
},
calculator() {
// 类型和输入值校验
this.alertMessage();
if (this.value === '存款利率') {
let id = this.durationOptions[0].id;
let money = this.inputRate;
requests.get('/api/deposit?money=' + money + '&duration=' + id).then(res => {
this.result = res.data
})
} else if (this.value === '贷款利率') {
let id = this.durationOptions[0].id;
let money = this.inputRate;
requests.get('/api/loan?money=' + money + '&duration=' + id).then(res => {
this.result = res.data
})
}
}
}
}
</script>
<style lang="less" scoped>
.el-row {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
.el-col {
border-radius: 4px;
}
.bg-purple-dark {
background: #99a9bf;
}
.bg-purple {
background: #d3dce6;
}
.bg-purple-light {
background: #e5e9f2;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.row-bg {
padding: 10px 0;
background-color: #f9fafc;
}
</style>
后端主要代码
package com.calculator.controller;
import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.calculator.entity.Calculator;
import com.calculator.entity.DepositRate;
import com.calculator.entity.LoanRate;
import com.calculator.service.CalculatorService;
import com.calculator.service.DepositRateService;
import com.calculator.service.LoanRateService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@RestController
@Slf4j
@CrossOrigin
public class CalculatorController {
@Resource
private CalculatorService calculatorService;
@Resource
private DepositRateService depositRateService;
@Resource
private LoanRateService loanRateService;
/**
* 执行表达式
*
* @param exp 表达式
* @return 计算结果
*/
@GetMapping("/calculator")
public double calculate(String exp) {
exp = exp.replaceAll(" ","+");
return calculatorService.execute(exp);
}
/**
* 查看记录
*
* @return 历史记录
*/
@GetMapping("/history")
public List<Calculator> history() {
return calculatorService.list(Wrappers.<Calculator>lambdaQuery()
.orderByDesc(Calculator::getCreateTime).last("limit 10"));
}
/**
* 计算存款利息
* @param money 存款金额
* @param duration 存款期限
*
* @return 利息
*/
@GetMapping("/deposit")
public BigDecimal depositInterest(String money, Integer duration){
if(StringUtils.isBlank(money)){
throw new RuntimeException("金额不能为空");
}
if(null == duration){
throw new RuntimeException("不能为空");
}
DepositRate depositRate= depositRateService.getOne(Wrappers.<DepositRate>lambdaQuery()
.eq(DepositRate::getId, duration));
return NumberUtil.mul(new BigDecimal(money), new BigDecimal(depositRate.getRate()));
}
/**
* 计算贷款利息
* @param money 存款金额
* @param duration 存款期限
*
* @return 利息
*/
@GetMapping("/loan")
public BigDecimal loanInterest(String money, Integer duration){
if(StringUtils.isBlank(money)){
throw new RuntimeException("金额不能为空");
}
if(null == duration){
throw new RuntimeException("不能为空");
}
LoanRate loanRate= loanRateService.getOne(Wrappers.<LoanRate>lambdaQuery()
.eq(LoanRate::getId, duration));
return NumberUtil.mul(new BigDecimal(money), new BigDecimal(loanRate.getRate()));
}
@GetMapping("/rateType")
public List<RateTypeDTO> getRateType(){
List<RateTypeDTO> rateTypeDTOList = new ArrayList<>();
RateTypeDTO depositRateDTO = new RateTypeDTO();
depositRateDTO.setId(1L);
depositRateDTO.setRateType("存款利率");
RateTypeDTO loanRateDTO = new RateTypeDTO();
loanRateDTO.setId(2L);
loanRateDTO.setRateType("贷款利率");
rateTypeDTOList.add(depositRateDTO);
rateTypeDTOList.add(loanRateDTO);
return rateTypeDTOList;
}
/**
* 获取存款周期利息
* @return 利息
*/
@GetMapping("/queryDeposit")
public List<DepositRate> queryDeposit(){
return depositRateService.list();
}
/**
* 获取贷款利息
* @return 利息
*/
@GetMapping("/queryLoan")
public List<LoanRate> queryLoan(){
return loanRateService.list();
}
@Data
public static class RateTypeDTO{
private Long id;
private String rateType;
}
}
package com.calculator.util;
import java.util.Stack;
public class CalculatorUtil {
private Stack<Character> operators; // 运算符栈
private Stack<Double> operands; // 操作数栈
public CalculatorUtil() {
operators = new Stack<>();
operands = new Stack<>();
}
public double evaluateExpression(String expression) throws IllegalArgumentException {
for (int i = 0; i < expression.length(); i++) {
char ch = expression.charAt(i);
if (ch == ' ') {
continue; // 忽略空格
} else if (Character.isDigit(ch) || ch == '.') {
StringBuilder sb = new StringBuilder();
sb.append(ch);
while (i + 1 < expression.length() && (Character.isDigit(expression.charAt(i + 1)) || expression.charAt(i + 1) == '.')) {
sb.append(expression.charAt(++i));
}
operands.push(Double.parseDouble(sb.toString()));
} else if (ch == '(') {
operators.push(ch);
} else if (ch == ')') {
while (!operators.isEmpty() && operators.peek() != '(') {
evaluateOperation();
}
if (!operators.isEmpty() && operators.peek() == '(') {
operators.pop();
} else {
throw new IllegalArgumentException("括号不匹配");
}
} else if (isOperator(ch)) {
while (!operators.isEmpty() && getPriority(operators.peek()) >= getPriority(ch)) {
evaluateOperation();
}
operators.push(ch);
} else {
throw new IllegalArgumentException("非法输入");
}
}
while (!operators.isEmpty()) {
evaluateOperation();
}
return operands.pop();
}
private boolean isOperator(char ch) {
return ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '%';
}
private int getPriority(char operator) {
switch (operator) {
case '+':
case '-':
return 1;
case '*':
case '/':
case '%':
return 2;
default:
return -1;
}
}
private void evaluateOperation() throws IllegalArgumentException {
char operator = operators.pop();
if (operands.isEmpty()) {
throw new IllegalArgumentException("非法输入");
}
double operand2 = operands.pop();
double operand1 = operands.isEmpty() ? 0 : operands.pop();
switch (operator) {
case '+':
operands.push(operand1 + operand2);
break;
case '-':
operands.push(operand1 - operand2);
break;
case '*':
operands.push(operand1 * operand2);
break;
case '/':
if (operand2 == 0) {
throw new IllegalArgumentException("除数不能为0");
}
operands.push(operand1 / operand2);
break;
case '%':
operands.push(operand1 % operand2);
break;
}
}
}
六、心路历程和收获
在本次制作计算器的过程中,的确收获了很多,学习到了很多vue、springboot、mysql的相关知识,过程挺坎坷的,最后bug没改完,作业是踩点交的,博客是踩点写的,很狼狈,写的很潦草,希望以后能避免这种情况的发生。这里也必须说一句,很感谢指点我的同学。虽然还有很多不足之处(没有做计算器的三角函数运算,利率计算器有很多问题等等),但是最后看见计算器能跑起来还是挺高兴的。