目录
作业信息
Calculator | |
---|---|
这个作业属于哪个课程 | https://bbs.csdn.net/forums/ssynkqtd-05 |
这个作业要求在哪里 | https://bbs.csdn.net/topics/617377308 |
这个作业的目标 | 实现前后端分离计算器 |
其他参考文献 | ... |
项目链接
0 PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 30 | 10 |
Estimate | 估计这个任务需要多少时间 | 20 | 10 |
Development | 开发 | 500 | 600 |
Analysis | 需求分析(包括学习新技术) | 400 | 1800 |
Design Spec | 生成设计文档 | 20 | 30 |
Design Review | 设计复审 | 30 | 20 |
Coding Standard | 代码规范(为目前的开发制定的规划) | 20 | 60 |
Design | 具体设计 | 30 | 70 |
Coding | 具体编码 | 600 | 1050 |
Code Review | 代码复审 | 60 | 20 |
Test | 测试(自我测试,修改代码并提交) | 120 | 160 |
Reporting | 报告 | 60 | 180 |
Test Repor | 测试报告 | 20 | 20 |
Size Measurement | 计算工作量 | 30 | 20 |
Postmorten&Process | 事后总结,并提出过程改进计划 | 30 | 30 |
合计 | 2070 | 4080 |
1 设计实现过程
功能结构图:
工具介绍
- 前端: 使用Vue 3 + Element Plus, 并使用 axios 库进行网络请求与后端进行数据交互,使用VSCode
- 后端: 使用 SpringBoot SpringMVC 和 Mybatis,对应端点请求和数据库交互 使用通用Mapper TKMapper 简化数据库操作,使用IDEA
- 数据库:使用 MySQL 来存储历史记录和利率信息,并通过 MyBatis 来进行数据库操作,用navicat可视化。
2 成品展示
1 基础计算器
1 基础运算
2 错误提示
3 读取历史
4 清零回退
5 科学计算
6 原型设计
计算器设计图如下:
设计参考百度计算器和360计算器,且观察到百度计算器和360计算器点击函数会返回函数加括号,如点击cos,返回cos( , 这样可以简化用户操作,更加直观,便于用户计算复杂的表达式,故参考了此设计,演示视频如下。
7 显示最近十条记录
在后端数据库中调取最近十条记录进行展示。展示界面和数据库信息如下:
8 扩展功能
分数四则运算
多重函数嵌套
2 利率计算器
1 计算存贷款利息
2 前端修改存贷款利息
在前端进行存贷款利率的修改,后端数据库数据也会相应变化。
演示视频如下:
3 利率设置
参照作业二中给出的两个表格进行利率设置,设置如下
3 主要代码说明
前端:App.vue
由于代码较多,故只选取了部分展示,代码中均有注释。
利率计算器部分
使用el-table组件 绑定 rateAList 实时显示利率并再为每一行提供编辑和删除两个按钮,分别绑定 editRow deleteRow方法,执行对应逻辑,因为添加调用的方法是同一个 故传入 1 2 区分添加的是贷款利率还是存款利率
<!-- 利率计算器部分 -->
<el-tab-pane label="利率计算器" name="second1">
<div
style="
width: 500px;
height: 500px;
display: flex;
flex-direction: column;
padding: 20px 20px;
background-color: #fff;
box-shadow: 0 0 200px -200px #000;
"
>
<!-- 内容输入框 通过此处输入 金额 时长 以供下面功能操作 -->
<el-form label-width="80">
<el-form-item label="金额">
<el-input v-model="amount" />
</el-form-item>
<el-form-item label="时长/年">
<el-input v-model="time" />
</el-form-item>
</el-form>
<!-- 两个按钮 提供 计算 贷款 计算存款的功能入口 调用 calculateHandle 执行逻辑 -->
<div style="display: flex">
<el-button type="primary" @click="calculateHandle(1)"
>计算贷款</el-button
>
<el-button
style="margin-left: 20px"
@click="calculateHandle(2)"
type="primary"
>计算存款</el-button
>
</div>
<!-- 结果显示 绑定 amountResult rateResult strDescResult 实时显示计算结果 -->
<h3>利息为:{{ amountResult }}元</h3>
<h3>计算利率为:{{ rateResult }}%,时长描述为:{{ strDescResult }}</h3>
</div>
</el-tab-pane>
<!-- 利率设置部分 -->
<el-tab-pane label="利率设置" name="second">
<div style="display: flex; justify-content: space-around">
<!-- 贷款利率部分 与计算历史实现逻辑相似 使用el-table组件 绑定 rateAList 实时显示贷款利率
并再每一行提供 编辑 和 删除按钮 分别绑定 editRow deleteRow 方法 执行对应逻辑
-->
<div style="width: 46%">
<h1>贷款利率</h1>
<el-table :data="rateAList" style="width: 100%">
<el-table-column prop="strDesc" label="描述"></el-table-column>
<el-table-column prop="time" label="时间/年"></el-table-column>
<el-table-column prop="rate" label="利率"></el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button-group>
<el-button size="small" @click="editRow(row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteRow(row)"
>删除</el-button
>
</el-button-group>
</template>
</el-table-column>
</el-table>
<el-button
type="primary"
style="width: 100%; margin-top: 20px"
@click="add(1)"
>添加</el-button
>
</div>
<!-- 同贷款利率 因为添加调用的方法是同一个 所以 传入 1 2 区分添加的贷款利率还是存款利率 -->
<div style="width: 46%">
<h1>存款利率</h1>
<el-table :data="rateBList" style="width: 100%">
<el-table-column prop="strDesc" label="描述"></el-table-column>
<el-table-column prop="time" label="时间/年"></el-table-column>
<el-table-column prop="rate" label="利率"></el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button-group>
<el-button size="small" @click="editRow(row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteRow(row)"
>删除</el-button
>
</el-button-group>
</template>
</el-table-column>
</el-table>
<el-button
type="primary"
style="width: 100%; margin-top: 20px"
@click="add(2)"
>添加</el-button
>
</div>
</div>
</el-tab-pane>
</el-tabs>
利率配置对话框
绑定 dialogFormVisible 实现显示和隐藏此对话框,可添加和修改利率内容
<el-dialog v-model="dialogFormVisible" title="利率配置" align-center="center">
<el-form :model="form" label-width="80">
<el-form-item label="时长描述">
<el-input v-model="form.strDesc" />
</el-form-item>
<el-form-item label="时长/年">
<el-input v-model="form.time" />
</el-form-item>
<el-form-item label="利率">
<el-input v-model="form.rate" />
</el-form-item>
</el-form>
<!-- 操作区 -->
<template #footer>
<span class="dialog-footer">
<!-- 关闭调用 reset 方法 -->
<el-button @click="reset()">关闭</el-button>
<!-- 点击确定后 执行 添加 或 修改 逻辑 -->
<el-button type="primary" @click="confirm()"> 确定 </el-button>
</span>
</template>
</el-dialog>
使用aniox作为请求工具
import axios from "axios";
// el loading 提供加载动画 elmessagebox 提供 确认框 elmessage 提供消息提醒功能
import { ElLoading, ElMessageBox, ElMessage } from "element-plus";
// 后端链接地址
const baseUrl = "http://localhost:8083";
// 加载动画的样式
const loadingObj = {
lock: true,
text: "Loading",
background: "rgba(255, 255, 255, 0.4)",
};
const headers = {
headers: {
"Content-Type": "application/json",
},
};
import { ref, reactive } from "vue";
const dialogFormVisible = ref(false);
// 对话框中内容的绑定 存储变量 对话框中输入数据 会自动绑定到 form 变量中
const form = reactive({});
// reset 方法 执行时 将 对话框中的内容全部重置为空 并隐藏对话框
const reset = () => {
Object.assign(form, {
id: undefined,
strDesc: undefined,
time: undefined,
rate: undefined,
createtime: undefined,
type: undefined,
});
// 此行代码代表隐藏对话框
dialogFormVisible.value = false;
};
// 确认 对话框确认方法
const confirm = () => {
// 首先关闭 对话框
dialogFormVisible.value = false;
// 调用elloading 将页面进入 加载状态
const loading = ElLoading.service(loadingObj);
console.log(form);
// 判断 form 变量id是否存在 不存在则代表是创建逻辑 存在则代表更新逻辑
if (form.id) {
// 更新逻辑 通过 axios 请求 updatehistory 端点 使后端执行更新历史的逻辑代码
// 此地需要传输json格式参数 所以 使用 了 headers 变量
axios
.post(baseUrl + "/updateHistory", form, headers)
.then((response) => {
// 请求响应时 关闭加载动画
loading.close();
// 刷新 页面内容
refreshPage();
})
.catch((error) => {
// 请求失败时 页面提示错误提示
errorMessage.value = "error";
});
} else {
// 添加逻辑 通过请求 addInterestrate 使后端执行添加利率的逻辑 headers 与更新一致
axios
.post(baseUrl + "/addInterestRate", form, headers)
.then((response) => {
// 关闭加载动画 刷新页面内容
loading.close();
refreshPage();
})
.catch((error) => {
// 请求错误 错误提醒
errorMessage.value = "error";
});
}
};
由于代码较多,界面样式以及基础计算器等部分未展示,可以查看代码文件,代码均有详细注释。
后端
提供接口
controller.java提供与计算器历史记录和利率相关的接口
提供了以下接口:
- queryHistory:查询计算器历史,按时间倒序排列,只返回前 10 条历史记录。
- sendHistory:保存计算器历史,将公式和计算时间保存到数据库中。
- addInterestRate:添加利率,将利率信息保存到数据库中。
- deleteInterestRate:删除利率,根据收益率 ID 从数据库中删除对应的收益率信息。
- queryInterestRate:查询利率,按创建时间升序排列,返回所有的收益率信息。
- updateHistory:更新利率,根据收益率 ID 更新对应的收益率信息。
使用了Lombok 提供的注解简化代码,如用 @Slf4j 注解生成的 slf4j 日志对象,@RequiredArgsConstructor 注解生成 final 字段构造器。使用 MyBatis 提供的操作数据库的 Mapper 对象来实现数据的增删改查。
// 所有端点接口
@RestController
@Slf4j
@RequiredArgsConstructor
public class Controller {
// 计算器历史数据库操作类
private final HistoryMapper historyMapper;
// 利率数据库操作类
private final InterestRateMapper interestRateMapper;
// 查询计算历史
@GetMapping("/queryHistory")
public Object queryHistory() {
// 查询 history表 按照时间排序
Example example = new Example(History.class);
example.orderBy("time").desc();
List<History> histories = historyMapper.selectByExample(example);
// 只显示10条
return histories.stream().limit(10).collect(Collectors.toList());
}
// 保存计算历史 将公式保存到 history表
@PostMapping("/sendHistory")
public Object sendHistory(@RequestBody History history) {
// 给该数据 设置 当前计算时间
history.setTime(new Date());
// 保存到数据表
historyMapper.insert(history);
return "ok";
}
// 添加利率
@PostMapping("/addInterestRate")
public Object addInterestRate(@RequestBody InterestRate rate) {
// 利率添加时间
rate.setCreatetime(new Date());
// 保存
interestRateMapper.insert(rate);
return "ok";
}
// 删除利率
@PostMapping("/deleteInterestRate")
public Object deleteInterestRate(String id) {
// 从数据库中删除
interestRateMapper.deleteByPrimaryKey(id);
return "ok";
}
// 查询利率
@PostMapping("/queryInterestRate")
public Object queryInterestRate() {
// 按照 时间 排序 利率
Example example = new Example(InterestRate.class);
example.orderBy("createtime").asc();
// 查询后响应前端
return interestRateMapper.selectByExample(example);
}
// 更新利率
@PostMapping("/updateHistory")
public Object updateHistory(@RequestBody InterestRate rate) {
// 从数据库中更新利率
interestRateMapper.updateByPrimaryKey(rate);
return "ok";
}
}
处理跨域请求
Spring MVC 中,默认情况下,浏览器限制了跨域请求,只能向同源的 URL 发送请求。为了允许跨域请求,需进行相应的配置。
MvcConfiguration 是一个实现了 WebMvcConfigurer 接口的类,通过重写其中的 addInterceptors 方法来添加一个拦截器。
CorsInterceptor 是一个自定义的拦截器,用于处理跨域请求。它会将跨域请求中的跨域资源共享相关的响应标头添加到 HTTP 响应中。
// 跨域设置 将该设置应用到 WebMvcConfigurer 设置
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CorsInterceptor()).addPathPatterns("/**").order(0);
}
}
public class CorsInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String origin = request.getHeader("Origin");
if (StringUtils.isBlank(origin)) {
return true;
}
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Headers", "*");
return true;
}
}
映射数据库中的表
// history 表对应的实体类
@Data
@Table(name = "calculator_history")
public class History {
@Id
// 表id
private Integer id;
// 公式
private String str;
// 结果
private String result;
// 时间 查询出来后 响应前端时 自动格式化
@JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Shanghai")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
private Date time;
}
// 利率表对应的实体类
@Data
@Table(name = "calculator_interest_rate")
public class InterestRate {
@Id
// 表id
private Integer id;
// 利率描述
@Column(name = "str_desc")
private String strDesc;
// 时间 年限
private String time;
// 利率
private String rate;
// 创建时间
private Date createtime;
// 类型 存款 贷款
private String type;
}
还有一些配置文件以及初始化数据库表的代码,由于较长,这里不再展示。
4 心路历程与收获
之前虽然也学过java,前端,mysql,但是知识点比较分散,缺乏系统的体系化的学习,也缺乏对一个完整的前后端分离的项目的理解与实践。为实现这个前后端分离的计算器以及让界面好看些,故新学了一些框架,Vue3+Element Plus和Spring Boot,MyBatis等,还是挺好用的,就是代码可能稍微有点繁琐,不过界面确实比第一次用python实现的那个计算器好看太多了,也算是很有收获。