目录
计算器页面
作业描述
属于哪个课程 | 2301-计算机学院-软件工程 |
---|---|
作业要求链接 | 第二次作业--前后端交互计算器 |
作业目标 | 实现一个前后端分离的科学计算器 含历史记录功能,并在此基础上实现利率计算器 可修改利率 |
参考文献 | vue帮助文档、Python常用框架:Flask - 知乎 (zhihu.com) HTML 基础 - 学习 Web 开发 | MDN (mozilla.org) CSS 语法 (w3school.com.cn) |
Gitcode仓库地址
前端:102103135-张文峰 / 基于Flask跟vue实现的前后端交互计算器-前端 · GitCode
后端:102103135-张文峰 / 基于Flask跟vue实现的前后端交互计算器-后端 · GitCode
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 15 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 15 |
Development | 开发 | 1350 | 1465 |
• Analysis | • 需求分析 (包括学习新技术) | 500 | 550 |
• Design Spec | • 生成设计文档 | 30 | 30 |
• Design Review | • 设计复审 | 15 | 15 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 45 | 45 |
• Design | • 具体设计 | 80 | 95 |
• Coding | • 具体编码 | 500 | 550 |
• Code Review | • 代码复审 | 60 | 60 |
• Test | • 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | 80 | 80 |
• Test Repor | • 测试报告 | 30 | 30 |
• Size Measurement | • 计算工作量 | 20 | 20 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1450 | 1560 |
基础计算器功能
功能1:加减乘除、取余、括号运算
-
基础+,-,* ,/,%,()运算
-
并将输入的字符串和结果存储到后端数据库中
功能2:清零回退
-
实现字符串输入的清零和回退功能
功能3:错误提示
-
在第一个功能正常的情况下增加非法输入提示,例如:0不能作为除数,括号不匹配等
功能4:读取历史记录
计算器右边显示十条从数据库调取的数据
拓展功能1:科学计算器
-
!(科学计算.gif)
利率计算器功能
功能1:计算存款、贷款利息
-
实现利率计算器,数据库中存储利率表(可随时修改利率)
扩展功能:前端修改存贷款利息
用户可根据当前的输入,修改存贷款的利率,同时将数据保存到数据库中,后端会自动根据数据库中是否有当前数据来自动决定删除、修改、添加操作
前端修改存款利率,并将数据保存到数据库中
前端修改贷款利率,并将数据保存到数据库中
拓展功能2:输入合法判断
自动判断输入是否合法,并给出提示
设计实现过程
知道作业要求后我的第一想法就是,第一次计算器应该写web计算器,这样这次就只需要写后端了。不过问题不大,上次作业实现的PC端计算器我是用python实现的,因此我的想法就是利用python的flask框架 配合前端,直接将表达式发送给后端,后端利用上次的设计,计算后返回结果。同时因为传输的都是原始数据,数据库可以将其直接保存,不需要进行额外的处理。
所以就开始着手学习前端三件套html、css、JavaScript,在学习过程中我了解到的Vue框架,所以就浅浅搭建了一个,但是因为学艺不精,只会使用new Vue来简化设计。
流程设计如下
代码实现
前端
html
框架
在框架中写入两个计算器的代码,并通过inshow这个变量来实现,哪些显示、哪些不显示,从而在一个网页中实现了层次化
<!--npm run dev-->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="css-1.css">
<meta charset="utf-8">
</head>
<body>
<div id="app" style="display: flex; align-items: center;">
<div class="calculator" v-show="inshow === 'cal'">
</div>
<div class="ratecal" v-if="inshow === 'rate' || inshow === 'rate_2'">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
<script src="js-1.js"></script>
</div>
</body>
</html>
科学计算器
<div class="result" style="grid-area: result">
{{ equation }}
</div>
<button style="grid-area: ac" @click="clear">AC</button>
...省略一大段按钮定义
<button style="grid-area: hl-cal" @click="now_rate">利率计算器</button>
<!-- <button style="grid-area: cal-record" @click="append(0)">历史记录</button> -->
<button style="grid-area: dot" @click="append('.')">.</button>
</div>
<div class="record" v-show="inshow === 'cal'">
<p>历史记录</p>
<p v-for="(item1, index) in record_for" :key="index">
{{ item1 }} = {{ record_res[index] }}
</p>
利率计算器
按钮之类的一个个慢慢写,这样在js以及后端实现函数时可以方便debug,显示利率表通过循环实现
<input type="text" class="text-box" placeholder="请输入金额(元)/利率(%)" v-model="money" style="grid-area: in_money" >
<input type="text" class="text-box" placeholder="请输入时间(年)" v-model="year" style="grid-area: in_year" >
<button style="grid-area: 存款" @click="switch_deposit">存款</button>
<button style="grid-area: 贷款" @click="switch_loan">贷款</button>
<button style="grid-area: kx-cal" @click="now_calculator">科学计算器</button>
<button style="grid-area: 计算" @click="cal_rate">计算</button>
<div class="result-2" style="grid-area: result">
利率是 : {{ equation }} 元
</div>
<button style="grid-area: 修改" @click="mod_rate">修改</button>
<p style="font-family: Helvetica; font-size: 15px; color:rgb(1, 99, 255); text-align: justify; width: 280px; height: 40px;">删除:年份存在且利率相同<br>修改:年份存在且利率不同<br>添加:年份不存在</p>
</div>
<div class="record" v-show="inshow === 'rate'">
<p>存款利率表</p>
<p v-for="(item1, index) in record_year" :key="index">
{{ item1 }} 年 : {{ record_money[index] }} %
</p>
</div>
<div class="record" v-show="inshow === 'rate_2'">
<p>贷款利率表</p>
<p v-for="(item1, index) in record_year" :key="index">
{{ item1 }} 年 : {{ record_money[index] }} %
</p>
</div>
<button
v-if="inshow === 'rate' || inshow === 'rate_2'"
style="font-family: Helvetica; font-size: 15px; color:rgb(1, 99, 255); text-align: justify; width: 100px; height: 40px; "
@click="Reset">重置利率表</button>
css
设计各按钮的布局,在这个地方掉了很多坑,像是多出来一大堆空白,以及压根没有界面等,还有编码问题,比如 ^ 这个符号如果直接出现在css里当作布局的名字的话,会出错,找了很久才找出这个bug
总体布局样式
body
{
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
max-height: 100vh;
margin: unset;
overflow: hidden;
background-color: #ffffff;
}
按钮布局
.calculator
{
--button-width: 80px;
--button-height: 80px;
border-color: #001940;
width: min-content;
height: fit-content;
display: grid;
grid-template-areas:
"result result result result result result"
"ac back percent divide sin asin"
"number-7 number-8 number-9 multiply cos acos"
"number-4 number-5 number-6 subtract ta atan"
"number-1 number-2 number-3 add lg ln"
"number-0 number-0 dot equal pow √"
"hl-cal hl-cal left right π e";
grid-template-columns: repeat(6, var(--button-width));
grid-template-rows: repeat(7, var(--button-height));
box-shadow: -8px -8px 16px -10px rgb(1, 99, 255), 8px 8px 16px -10px rgba(1, 99, 255);
padding: 24px;
border-radius: 20px;
}
按钮以及表达式样式
.calculator button
{
margin: 8px;
padding: 0;
border: 0;
display: block;
outline: none;
border-radius: calc(var(--button-height) / 2);
font-size: 24px;
font-family: Helvetica;
font-weight: normal;
color:rgb(1, 99, 255);/* #999;*/
background: linear-gradient(135deg, rgba(230, 230, 230, 1) 0%, rgba(246, 246, 246, 1) 100%);
box-shadow: -4px -4px 10px -8px rgba(255, 255, 255, 1), 4px 4px 10px -8px rgba(0, 0, 0, .3);
}
.calculator button:active
{
box-shadow: -4px -4px 10px -8px rgba(255, 255, 255, 1) inset, 4px 4px 10px -8px rgba(0, 0, 0, .3) inset;
}
.result {
text-align: right;
line-height: var(--button-height);
font-size: 48px;
font-family: Helvetica;
padding: 0 20px;
color:rgb(1, 99, 255);/* #666;*/
}
javascript
Vue框架中唯一用到的操作,new Vue
new Vue({
el: '#app',
data: {
equation: '',
inshow : 'cal', //cal rate table
record_for : [],
record_res : [],
record_money : [],
record_year : [],
money: '',
year:'',
type:0,/* 0 为存款 1为贷款*/
},
methods:
{
}
})
各种方法的定义
如添加入表达式,向后端发送请求等
backspace() 退格
clear() 清零
calculate() 向后端发送计算的请求,后端处理表达式后返回该表达式的答案,并记录在数据库中
get_rate() 向后端发送显示利率表的请求,后端从数据库调取数据后返回给前端
其他功能实现方法相似,不再赘述
append(character)
{
this.equation += character
},
backspace()
{
this.equation = this.equation.slice(0, -1)
},
clear() { this.equation = ''},
calculate()
{
fetch("http://127.0.0.1:8081/get/cal", {
method: "POST",
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify({ equation: this.equation }),
})
.then(response => {
if (!response.ok) {
this.equation = 'Network response was not ok--1'
throw new Error('Network response was not ok');
}
return response.json(); // 将响应解析为JSON格式
})
.then(data => {
this.equation = data['equation']
this.record_for = data['record_for']
this .record_res = data['record_res']
})
.catch(error => {
this.equation =error
});
},
get_rate()
{
fetch("http://127.0.0.1:8081/get/rate", {
method: "POST",
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify({ type:this.type}),
})
.then(response => {
if (!response.ok) {
this.equation = '语法错误'
return
}
return response.json(); // 将响应解析为JSON格式
})
.then(data => {
this.record_year = data['record_year']
this.record_money = data['record_money']
})
},
now_rate() { this.record_year = '',this.record_money = '',this.equation = '0' , this.type = 0,this.inshow = 'rate' ,this.get_rate() },
// , this.get_rate()
now_calculator() { this.equation = '', this.type = 0, this.inshow = 'cal' },
switch_deposit(){ this.record_year = '',this.record_money = '',this.equation = '0', this.type = 0, this.inshow = 'rate', this.get_rate()},
switch_loan(){this.record_year = '',this.record_money = '',this.equation = '0', this.type = 1, this.inshow = 'rate_2', this.get_rate()},
后端python
Flask框架
通过flask,我们可以定义相关函数来相应前端
import math
import pandas as pd
from flask import Flask, request, jsonify
from flask_cors import cross_origin,CORS
import pymysql
import openpyxl
import json
import urllib
#npm run dev -- --port 8081
#port_num = 8082
app = Flask(__name__)
CORS(app)
if __name__ == "__main__":
app.config["JSON_AS_ASCII"] = False
app.run(host="127.0.0.1", port=8081)
#app.run(host="192.168.40.182", port=8080)
科学计算器功能
表达式转化
由于前端传给后端的是原始数据,所以我们要先进行相关处理后才能进行计算
@app.route("/get/cal", methods=["GET", "POST"])
@cross_origin()
def cal():
data = request.get_json()
cal = data['equation']
cal=cal.replace('√(','math.sqrt(')
cal=cal.replace('arcsin(','math.as#in(')
cal=cal.replace('arccos(','math.ac#os(')
cal=cal.replace('arctan(','math.at#an(')
cal=cal.replace('sin(','math.sin(')
cal=cal.replace('cos(','math.cos(')
cal=cal.replace('tan(','math.tan(')
cal=cal.replace('^','**')
cal=cal.replace('ln(','math.log(')
cal=cal.replace('lg(','math.log10(')
cal=cal.replace('e','math.e')
cal=cal.replace('π','math.pi')
cal=cal.replace('÷','/')
cal=cal.replace('×','*')
cal=cal.replace('#','')
try:
ans=eval(cal)
except ZeroDivisionError:
ans = "除零错误"
except:
ans = "语法错误"
数据库部分(历史记录)
由于限定数据库的历史记录为十条,因此我们将其按id排序,当总数大于10时,将id为1的删去,并将其余数据id减一
同时,相比于利用各种库,我们直接采用数据库的原始语句,方便我们可以直接将其拿去数据库软件中进行操作来debug
注意数据库返回的数据是以\n进行分割的 ,如果不将其处理成数组(一维表)的形式,在前端显示时将会出错
now_for = str(data['equation'])
now_res = str(ans)
db = pymysql.connect(host='localhost', port = 3306,user='root', password='123456',database='calculator')
cursor = db.cursor()
sql = 'SELECT * FROM calculator.history'
num = cursor.execute(sql)
num = num + 1
cmd = f"""INSERT INTO calculator.history (id, form, res) VALUES ({num}, '{now_for}', '{now_res}')"""
cursor.execute(cmd)
db.commit()
if(num > 10):
cmd = """update calculator.history set id = id - 1"""
cursor.execute(cmd)
db.commit()
cmd = """DELETE FROM calculator.history WHERE id < 1 or id >10"""
cursor.execute(cmd)
db.commit()
sql='select * from calculator.history'
df1=pd.read_sql(sql,db)
form = df1['form']
res = df1['res']
form = form.to_string(index = False)
res = res.to_string(index = False)
form = form.split('\n')
res = res.split('\n')
cursor.close()
db.close()
return jsonify({"equation":str(ans),"record_for":form,'record_res':res})
利率计算器
数据处理部分
前端由于是用户输入,传过来的数据可能会出错,我们将其处理后,如果出现错误,返回给用户相应提示
def cal_money():
data = request.get_json()
now_type = data['type']
now_money = 0
now_year = 0
db = pymysql.connect(host='localhost', port = 3306,user='root', password='123456',database='calculator')
try:
now_money = float(data['money'])
now_year = float(data['year'])
except ValueError:
return jsonify({"equation":str("语法错误")})
从数据库读取利率表并计算
其中now_type是判断存款或者贷款的一个变量
要通过它来选择读取数据库的哪个表的数据
if(now_type == 0):
sql='SELECT * FROM calculator.deposit_rate order by year'
df1=pd.read_sql(sql,db)
year = df1['year']
year = year.to_string(index =False)
year = year.split('\n')
rate = df1['rate']
rate = rate.to_string(index =False)
rate = rate.split('\n')
for i in range(len(year)-1,-1, -1):
if(now_year >= float(year[i])):
ans = now_year * now_money * float(rate[i])
break
else:
...
ans =round(ans/100,2)
db.close()
return jsonify({"equation":str(ans)})
前端修改利率部分
依次判断:是不是删除操作、是不是修改操作、是不是添加操作
db = pymysql.connect(host='localhost', port = 3306,user='root', password='123456',database='calculator')
cursor = db.cursor()
data = request.get_json()
now_type = data['type']
if(now_type == 1):
dbname= "loan_rate"
db = pymysql.connect(host='localhost', port = 3306,user='root', password='123456',database='calculator')
cursor = db.cursor()
cmd = f"""SELECT * FROM calculator.{dbname} where year = {now_year} and rate = {now_rate}"""
t = cursor.execute(cmd),db.commit()
if(t[0]>=1):
cmd = f"""DELETE FROM calculator.{dbname} where year = {now_year} and rate = {now_rate}"""
cursor.execute(cmd),db.commit()
else:
cmd = f"""SELECT * FROM calculator.{dbname} where year = {now_year}"""
t = cursor.execute(cmd),db.commit()
if(t[0]>=1):
cmd = f"""UPDATE calculator.{dbname} set rate = {now_rate} where year = {now_year}"""
cursor.execute(cmd),db.commit()
else:
cmd = f"""INSERT INTO calculator.{dbname} (year,rate) VALUES ({now_year}, {now_rate})"""
cursor.execute(cmd),db.commit()
排序后并进行小数等处理后返回数据
因为数据库返回的数据分割后直接传给前端后它会以科学计数法的形式显示,有十二位小数,比较不符合正常需求,因此对其做一下处理
sql=f'SELECT * FROM calculator.{dbname} order by year'
df1=pd.read_sql(sql,db)
print(df1)
cursor.close()
db.close()
year = df1['year']
year = year.to_string(index =False)
year = year.split('\n')
rate = df1['rate']
rate = rate.to_string(index =False)
rate = rate.split('\n')
for i in range(len(year)):
year[i]=float(year[i])
rate[i]=float(rate[i])
return jsonify({"record_year":year,'record_money':rate })
心路历程与收获
这两周考试很多,还得抽时间出来学习前端,而且因为我一开始将数据库理解错了,并没有想到是用数据库软件,因此我就采用python操纵excel的方式来模拟数据库,这个坑踩了非常非常多,我都快写完了,我朋友提醒我,要用数据库,问了助教我才意识到是原来我理解错了。不过这个踩的坑倒是比excel少多了(毕竟只有我一个憨憨用excel,用数据库可以问同学),数据库一开始安装什么的都是看着视频、叫朋友帮忙,才搭建完的。
这个过程最后看来是痛并快乐着吧,从一开始的一头雾水,到最后成功实现这个计算器,收获了很多,比如css、html、js这三个东西我是通过这次作业才接触到的,之前一直在研究算法。而且在vue框架下,这三件套配合起来还是挺简单的,一开始我压根不知道他们三个能够相互配合,只知道一般做前端都是用这三件套,这次一步步实现下来,通过在vue的类中写上各种提供html调用的方法实现前后端通信,通过在html定义按钮、文本框等、在css中对他们的排版进行合理布局……林林总总,收获匪浅(代码低耦合度改起来是真的快,还不用担心会影响到之前写好的)
最让我满意的应该就是接口的设计了,后端的接口合起来只有四个变量,通过不同函数将功能拆分开。
不过,相比于看文档,我还是更习惯先写再看的形式,由功能出发,查询相关代码怎么写,写完遇到问题,查查百度、问问ai,然后再看文档,记忆会很深刻。希望能在未来的学习中进一步提高自己吧!