I. 文章概览
在这篇博文中,我将详细介绍前端和后端交互式Web计算器的实现,并展示最终产品。
我制作的这个计算器可以实现加、减、乘、除、清零、幂函数、三角函数、指数函数、反三角函数和ans运算。
Link to the finished project code: Assignment2 codes in github
II. 作者与作品的基本信息
Course for This Assignment | 2301-MUSE社区-CSDN社区云 |
Assignment Requirements | Second assignment-- Back-end separation calculator programming-CSDN社区 |
The Aim of This Assignment | Improve the function of calculator and realize front-end interaction |
MU STU ID and FZU | 21126259_832101125 |
目录
I. 文章概览
III. PSP表格
IV. 成品界面展示
①网页调试图
② 基础运算
③三角函数计算
④ 反三角函数计算
④ 对数函数计算
⑤ 历史记录存储
V. 项目设计思路
①前端设计思路:
②后端设计思路:
VII. 代码解释
①前端代码解释
a. Java Script
b. HTML
c. Css
②Back-end Programming Implementation (Python)
VII. 项目未来规划
VIII. 设计参考文献
III. PSP表格
PSP | Estimated time (minutes) | Actual time (minutes) |
Planning | ||
• Estimate: | 40 | 40 |
Development | ||
• Analysis | 30 | 30 |
• Design Spec | 20 | 20 |
• Design Review | 10 | 10 |
• Coding Standard: | 20 | 20 |
• Design | 30 | 30 |
• Coding | 300 | 500 |
• Code Review | 100 | 100 |
• Test | 20 | 30 |
Reporting | ||
• Test Report: | 5 | 5 |
• Size Measurement | 10 | 10 |
• Postmortem & Process Improvement Plan | 10 | 10 |
TOTAL TIME | 595 | 805 |
IV. 成品界面展示
①网页调试图
② 基础运算
③三角函数计算
sin(x):
cos(x):
tan(x):
④ 反三角函数计算
asin(x):
acos(x):
atan(x):
④ 对数函数计算
⑤ 历史记录存储
V. 项目设计思路
①前端设计思路:
a. HTML 结构:创建一个 HTML 文件来定义网页的结构。添加输入字段、按钮和其他元素,使用户能够输入数学表达式并进行计算。
b. CSS 样式:使用 CSS 美化网页,使其看起来吸引人并易于使用。
c. JavaScript 交互:使用 JavaScript 编写前端逻辑,实现以下功能:
监听按钮点击事件以捕获用户输入。
动态显示用户输入的数学表达式在输入框中。
处理用户点击等号按钮时的表达式求值。
在网页上显示计算结果。
d. 用户体验(UX):确保用户界面友好且易于使用。添加错误处理,如除零错误、反三角函数超过阈值错误等,以提高用户体验。
②后端设计思路:
a. Python 后端:使用 Python 编写后端代码,使用 PyCharm 进行开发。后端的主要作用是处理来自前端的请求,执行计算操作,并将结果返回给前端。
b. API 设计:设计两个 API,分别用于存储和读取。
c. 数据库连接:使用 PyMySQL 连接数据库并执行必要的数据库操作,以存储计算历史或其他数据。
d. 结果返回:将存储的表达式及其对应的计算结果以 JSON 格式发送回前端。务必处理任何潜在的错误或异常,以提供友好的错误消息。
e. 部署:将 Python 后端部署到本地服务器,确保它能够响应前端的请求。
VII. 代码解释
①前端代码解释
a. Java Script
// 这个函数用于将输入添加到显示框中 function display(input) { const str = document.getElementById("text"); str.value += input; } // 这个异步函数执行计算,处理数学表达式 async function equals() { const str = document.getElementById("text"); let input = str.value; let lastExpression = input; // 如果输入包含 'Ans',尝试获取答案并替换 'Ans' 为答案的值 if (input.includes('Ans')) { try { const ansValue = await getAns(); input = input.replace(/Ans/g, ansValue); } catch (error) { console.error('获取答案出错: ' + error); } } // 处理三角函数和反三角函数 if (input.includes('asin') || input.includes('acos')) { const asinMatch = input.match(/asin\(([^)]+)\)/); const acosMatch = input.match(/acos\(([^)]+)\)/); if (asinMatch) { const x = parseFloat(asinMatch[1]); if (x < -1 || x > 1) { str.value = "Error: Invalid input for asin(x). x must be in the range [-1, 1]."; return; } } if (acosMatch) { const x = parseFloat(acosMatch[1]); if (x < -1 || x > 1) { str.value = "Error: Invalid input for acos(x). x must be in the range [-1, 1]."; return; } } } // 替换 '^' 运算符为 '**' if (input.includes('^')) { input = input.replace(/\^/g, '**'); } // 替换三角函数和反三角函数为 Math 对象的方法 if (input.includes('asin') || input.includes('acos') || input.includes('atan')) { input = input.replace(/(\b(asin|acos|atan))\(/g, 'Math.$2('); } // 替换 sin, cos, tan 为 Math.sin, Math.cos, Math.tan if (input.includes('sin') || input.includes('cos') || input.includes('tan')) { input = input.replace(/(\b(sin|cos|tan))\(/g, 'Math.$2('); } // 替换 'e' 为 Math.E,'π' 为 Math.PI if (input.includes('e')) { input = input.replace(/e/g, 'Math.E'); } if (input.includes('π')) { input = input.replace(/π/g, 'Math.PI'); } // 处理对数函数 if (input.includes('log')) { input = input.replace(/log\(([^)]+)\)\(([^)]+)\)/g, 'Math.log($2) / Math.log($1)'); } // 替换 'ln' 为 Math.log if (input.includes('ln')) { input = input.replace(/ln/g, 'Math.log'); } // 替换 'Ans' 为上次计算的结果 if (input.includes('Ans')) { let ansValue = getAns(); input = input.replace(/Ans/g, ansValue); } // 处理除以 0 的情况 if (input.includes('/0')) { str.value = "Error: Division by zero is not allowed"; return; } try { // 使用 eval 函数计算表达式 const result = eval(input); str.value = result; // 创建一个 XMLHttpRequest 对象并发送 POST 请求 const xhr = new XMLHttpRequest(); xhr.open('POST', 'http://localhost:5000/post', true); xhr.setRequestHeader('Content-type', 'application/json'); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { const response = xhr.responseText; console.log(response); } else { console.error('请求失败,状态码:' + xhr.status); } } }; const data = { expression: lastExpression, result: result }; xhr.send(JSON.stringify(data)); } catch (error) { str.value = "Error"; } } // 删除最后一个字符 function back() { const str = document.getElementById("text"); str.value = str.value.substring(0, str.value.length - 1); } // 重置显示框 function reset() { const str = document.getElementById("text"); str.value = ""; } // 将常数 Math.E 插入到显示框 function insertE() { const str = document.getElementById("text"); str.value += Math.E; } // 在 getAns 函数中返回 Promise,用于获取答案 function getAns() { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', 'http://localhost:5000/get', true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { const Data = JSON.parse(xhr.responseText); const array = Data["data"]; console.log(array); resolve(array[0][1]); } else { console.error('获取数据出错: ' + xhr.status); reject(xhr.status); } } }; xhr.send(); }); } // 获取历史记录 function getHistory() { const xhr = new XMLHttpRequest(); xhr.open('GET', 'http://localhost:5000/get', true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { Data = JSON.parse(xhr.responseText); array = Data["data"]; console.log(array); let string = ""; for (let i = 0; i < array.length; i++) { string += array[i][0] + " = " + array[i][1]; string += '\n'; } } else { console.error('获取数据出错: ' + xhr.status); } } }; xhr.send(); }
b. HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Simple Calculator in Web</title>
<link href="css/PageSetting.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/CalculatorFunction.js"></script>
<!-- <script type="text/javascript" src="js/history.js"></script> -->
<!-- <script type="text/javascript" src="js/localServer.js"></script> -->
</head>
<body>
<div id="calculator">
<div id="head"><h3>Simple Calculator in Web</h3></div>
<div id="show" align="center"><input type="text" id="text" ></div>
<div id="cuttom">
<table align="center">
<tr>
<td><input type="button" value="e" onclick="display('e')"></td>
<td><input type="button" value="π" onclick="display('π')"></td>
<td><input type="button" value="(" onclick="display('(')"></td>
<td><input type="button" value=")" onclick="display(')')"></td>
<td><input type="button" value="^" onclick="display('^')"></td>
<td><input type="button" value="history" onclick="getHistory()"></td>
</tr>
<tr>
<td><input type="button" value="log" onclick="display('log(')"></td>
<td><input type="button" value="ln" onclick="display('ln(')"></td>
<td><input type="button" value="ans" onclick="display('Ans')"></td>
<td><input type="button" value="asin" onclick="display('asin(')"></td>
<td colspan="2"><input type="button" value="←" onclick="back()"></td>
</tr>
<tr>
<td><input type="button" value="." onclick="display('.')"></td>
<td><input type="button" value="0" onclick="display(0)"></td>
<td><input type="button" value="acos" onclick="display('acos(')"></td>
<td><input type="button" value="atan" onclick="display('atan(')"></td>
<td colspan="2"><input type="button" value="c" onclick="reset()"></td>
</tr>
<tr>
<td><input type="button" value="7" onclick="display(7)"></td>
<td><input type="button" value="8" onclick="display(8)"></td>
<td><input type="button" value="9" onclick="display(9)"></td>
<td><input type="button" value="+" onclick="display('+')"></td>
<td><input type="button" value="-" onclick="display('-')"></td>
<td><input type="button" value="tan" onclick="display('tan(')"></td>
</tr>
<tr>
<td><input type="button" value="4" onclick="display(4)"></td>
<td><input type="button" value="5" onclick="display(5)"></td>
<td><input type="button" value="6" onclick="display(6)"></td>
<td><input type="button" value="*" onclick="display('*')"></td>
<td><input type="button" value="/" onclick="display('/')"></td>
<td><input type="button" value="sin" onclick="display('sin(')"></td>
</tr>
<tr>
<td><input type="button" value="1" onclick="display(1)"></td>
<td><input type="button" value="2" onclick="display(2)"></td>
<td><input type="button" value="3" onclick="display(3)"></td>
<td colspan="2"><input type="button" value="=" onclick="equals()"></td>
<td><input type="button" value="cos" onclick="display('cos(')"></td>
</tr>
</table>
</div>
</div>
</body>
</html>
c. Css
body {
background: #f0f0f0;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
#calculator {
width: 600px;
margin: 50px auto;
border: 1px solid #ccc;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-color: #fff;
border-radius: 10px; /* 添加圆角 */
padding: 20px; /* 增加内边距 */
}
#head {
background-color: #333;
color: #fff;
text-align: center;
padding: 10px 0;
border-radius: 10px; /* 添加圆角 */
}
#head h3 {
margin: 0;
font-size: 24px;
}
#show input {
width: 100%;
height: 60px;
font-size: 30px;
text-align: right;
border: 2px solid black;
padding: 10px;
outline: none;
box-sizing: border-box;
border-radius: 10px; /* 添加圆角边框 */
}
#custom {
padding: 20px;
}
table {
width: 100%;
table-layout: fixed;
margin-top: 15px; /* 上方间隔 */
margin-bottom: 15px; /* 下方间隔 */
}
table td {
width: 25%;
margin: 10px; /* 左右间隔 */
}
table input {
width: 100%;
height: 50px;
font-size: 20px;
text-align: center;
border: none;
outline: none;
cursor: pointer;
background: #f0f0f0;
border-radius: 10px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); /* 添加阴影效果 */
transition: transform 0.2s, box-shadow 0.2s; /* 添加过渡效果 */
}
/* 鼠标悬停时应用立体效果和放大效果 */
table input:hover {
background: #ccc;
transform: scale(1.1); /* 放大效果 */
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2), 0 0 0 2px #000; /* 增加阴影深度和黑色边框 */
}
②Back-end Programming Implementation (Python)
/post
API:
- 用于存储操作表达式和值。 客户端可以向该接口发送一个 POST 请求,传递一个包含表达式和计算结果的 JSON 数据。
- 该接口将接收到的数据插入数据库,并确保保留最新的 10 条记录,删除多余的记录。
- 返回一个 JSON 响应,包括消息 "ok" 表示操作成功,或错误消息和状态码 500 表示操作失败。
@app.route('/post', methods=['POST'])
def post_history(): # 存储运算表达式和值
try:
data = request.get_json() # 获取POST请求的JSON数据
expression = data.get('expression')
result = data.get('result')
time = datetime.datetime.now()
# 插入新数据
data = (time, expression, result)
insert = "INSERT INTO history (time, expression, result) VALUES (%s, %s, %s)"
cursor.execute(insert, data)
# 获取当前记录数量
cursor.execute("SELECT COUNT(*) FROM history")
record_count = cursor.fetchone()[0]
# 如果记录数量超过十条,删除最旧的记录
if record_count > 10:
delete_old_records = "DELETE FROM history ORDER BY time LIMIT %s"
cursor.execute(delete_old_records, (record_count - 10,))
conn.commit()
response_message = "ok"
return jsonify({"message": response_message})
except Exception as e:
error_message = str(e)
return jsonify({"error": error_message}), 500
#用于存储运算表达式和值。
#客户端可以向此接口发送 POST 请求,传递一个 JSON 数据包括表达式和计算结果。
#该接口会将接收到的数据插入数据库中,并确保保留最新的 10 条记录,删除多余的记录。
#返回一个 JSON 响应,包括消息 "ok" 表示操作成功,或者包括错误消息和状态码 500 表示操作失败。
/get
API:
- 用于获取历史操作表达式和结果。 客户端可以向该接口发送一个 GET 请求,以获取最新的 10 条历史记录。
- 该接口在数据库中查询历史记录,并返回一个包含表达式和结果数据的 JSON 响应。
- 如果查询失败,将返回一个错误消息和状态码 500。
@app.route('/get', methods=['GET'])
def get_calculation_data(): # 得到历史值
try:
cursor.execute("SELECT expression, result FROM history ORDER BY time DESC LIMIT 10")
data = cursor.fetchall()
return jsonify({"data": data})
except Exception as e:
error_message = str(e)
return jsonify({"error": error_message}), 500
#用于获取历史运算表达式和结果。
#客户端可以向此接口发送 GET 请求,以获取最新的 10 条历史记录。
#该接口会查询数据库中的历史记录,并返回一个 JSON 响应,其中包括表达式和结果的数据。
#如果查询失败,将返回错误消息和状态码 500。
VII. 项目未来规划
通过设置新的交互栏和新的计算逻辑对用户进行分类,使特定用户可以直接使用与预期用途相关的计算键,如税收计算、游戏装备损坏计算等功能。
VIII. 设计参考文献
- 【精选】2023 年 MySQL 8.0 安装配置 最简易(保姆级)_mysql8.0安装配置教程_mobeicanyue的博客-CSDN博客
- web 技术中的前端和后端是如何交互的 - 知乎 (zhihu.com)
- MySQL 管理 | 菜鸟教程 (runoob.com)