Front-end and back-end separation projects based on Node.js and express frameworks and native JS: One step closer!
文章目录
- Front-end and back-end separation projects based on Node.js and express frameworks and native JS: One step closer!
Before Start
Ha ha, it’s me again! As you can see, this is second assignment for the course Software Engineering.
First let me record some basic information.
Course for This Assignment | Software Engineering |
---|---|
Assignment Requirements | Actual Program(A calculator) and a blog |
Objectives of This Assignment | Implement a calculator based on web |
MU ID and FZU ID | 21126135 | 832102117 |
Description
This blog will show you how to write front-end and back-end code to implement a web calculator that can recover history.
Technology stack
Front-end: native html+JS+css
Back-end: Node.js+express
Git Repository Link and Code Standards Link
PSP Table
Personal Software Process Stages | Estimated Time(minutes) | Actual Time(minutes) |
---|---|---|
Planning | 15 | 7 |
• Estimate | 15 | 10 |
Development | 300 | 500 |
• Analysis | 10 | 5 |
• Design Spec | 10 | 5 |
• Design Review | 10 | 5 |
• Coding Standard | 5 | 5 |
• Design | 5 | 5 |
• Coding | 220 | 410 |
• Code Review | 10 | 10 |
• Test | 30 | 55 |
Reporting | 50 | 50 |
• Test Repor | 10 | 13 |
• Size Measurement | 15 | 25 |
• Postmortem & Process Improvement Plan | 25 | 13 |
Sum | 365 | 557 |
Presentation of the Finished Product
演示
Design and Implementation Process
Implemented features
✔️On the basis of the last experiment, we added and improved a variety of functions, including the remainder (%), power, root and other operations in addition to the basic addition, subtraction, multiplication and division
✔️More like a scientific calculator, supporting sin, cos, log and root operations
✔️Use Node.js and express to build a basic local server to receive and send http requests
✔️The historical record of the last calculation can be restored
✔️Better looking UI
Design idea
-
Add more calculator buttons on the basis of the original, improve the existing functions
-
Change the overall layout of the calculator design
-
Use the browser’s own asynchronous ajax request api to try to connect with the server running on Node.js locally
-
Record the calculation process and save it as a text file
Code Explanation
Front-end
Project structure
Code rendering
First of all, I want to state again: this assignment will not repeat the code explanation of the first assignment, if you are confused by the following code, please go to my first assignment.
This is the front end, and I’ll just explain the core code, which is the native JS that I added based on the first assignment.
/*
* @Descripttion: 计算器核心逻辑
* @Author: Chen Zhengyi
* @Date: 2023-10-10 19:36:13
* @Latest Update: 2023-10-22 20:13
*/
//js主线程
var panel = document.getElementsByClassName("operation-zone")[0];
var btnNameList = ["C", "B", "/", "*", "ans", "7", "8", "9", "-", "%", "4", "5", "6", "+", "√", "1", "2", "3", "log", "=", ".", "0", "^", "sin", "cos"];
initButton(25);
var baseURL = "http://127.0.0.1:8081"
// 以下是各绑定函数
//1.cal()函数负责对输入内容进行基本计算,包括计算顺序、取值,涵盖加减乘除和幂运算等
function calc(value) {
//定义运算符数组
let operators = [];
//使用正则表达式匹配基本运算符号,即加减乘除幂,并将计算数存入数组
let nums = value.split(/[\+\-\*\/\^%]/);
for (let i = 0; i < value.length; i++) {
if ((value[i] == '+' || value[i] == '-' || value[i] == '*' || value[i] == '/' || value[i] == '^' || value[i] == '%')) {
if ((i != 0 && i != value.length - 1) && (value[i + 1] != '+' && value[i + 1] != '-' && value[i + 1] != '*' && value[i + 1] != '/') && value[i + 1] != '^' && value[i + 1] != '%') {
operators.push(value[i]);
}
else if (i == 0 && (value[i] == '+' || value[i] == '-')) {
nums[0] = '0';
//对出现在第一位的运算符号判断合理性
operators.push(value[i]);
}
//若不满足匹配条件,则返回error
else {
return "error";
}
}
}
//根据数组和运算符号进行运算,先乘除幂余,后加减
for (let i = 0; i < operators.length; i++) {
if (numSwitch(nums[i]) != "error" && numSwitch(nums[i + 1]) != "error") {
if (operators[i] == '*') {
let product = numSwitch(nums[i]) * (numSwitch(nums[i + 1]))
nums[i] = "" + product;
nums.splice(i + 1, 1);
operators.splice(i, 1);
i = -1;
}
else if (operators[i] == '/') {
if (numSwitch(nums[i + 1]) != 0) {
let division = numSwitch(nums[i]) / (numSwitch(nums[i + 1]));
nums[i] = "" + division;
nums.splice(i + 1, 1);
operators.splice(i, 1);
i = -1;
}
else { return "error" }
}
else if (operators[i] == '^') {
let power = Math.pow(numSwitch(nums[i]), numSwitch(nums[i + 1]));
nums[i] = "" + power;
nums.splice(i + 1, 1);
operators.splice(i, 1);
i = -1;
}
else if (operators[i] == '%') {
if (numSwitch(nums[i + 1]) != 0) {
let remain = numSwitch(nums[i]) % (numSwitch(nums[i + 1]));
nums[i] = "" + remain;
nums.splice(i + 1, 1);
operators.splice(i, 1);
i = -1;
}
else { return "error" }
}
}
else { return "error" }
}
for (let i = 0; i < operators.length; i++) {
if (operators[i] == '+') {
let addition = numSwitch(nums[i]) + (numSwitch(nums[i + 1]))
nums[i] = "" + addition;
nums.splice(i + 1, 1);
operators.splice(i, 1);
i = -1;
}
else if (operators[i] == '-') {
let substraction = numSwitch(nums[i]) - (numSwitch(nums[i + 1]))
nums[i] = "" + substraction;
nums.splice(i + 1, 1);
operators.splice(i, 1);
i = -1;
}
}
if (operators[0] == null) {
return numSwitch(nums[0]);
}
else return "error";
}
//3.adjustBtn() 调整按钮的数量布局和大小
function adjustBtn(btn) {
btn.style.height = btn.offsetWidth + "px";
}
//4.initButton() 初始化按钮
function initButton(btnNum) {
for (let i = 0; i < btnNum; i++) {
let btn = document.createElement("button");
panel.appendChild(btn);
btn.setAttribute("class", "btn");
//设置DOM元素属性并转化为实际html元素渲染
btn.setAttribute("id", btnNameList[i]);
let id = btn.getAttribute("id");
btn.setAttribute("value", id);
btn.innerHTML = id;
let addFun = "oper('" + id + "')";
btn.setAttribute("onclick", addFun);
adjustBtn(btn);
}
}
//5.oper() 按钮绑定事件
function oper(value) {
let inputZone = document.getElementById("input");
let input = inputZone.value;
if (value == '=') {
const xhr = new XMLHttpRequest();
//2.配饰请求方法,设置POST请求接口地址
xhr.open('post', baseURL + "/api/addStorage");
xhr.setRequestHeader('Content-Type', 'text/plain')
//3.发送请求
xhr.send(input);
//4.网络请求返回的数据
// xhr.readystate===4代表响应完成了,xhr.status === 200 代表请求成功
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
alert("ok");
}
}
inputZone.value = calc(input);
}
else if (value == 'B') {
let newVal = input.substring(0, input.length - 1);
// document.getElementById("input").setAttribute("value", newVal);
inputZone.value = newVal;
}
else if (value == 'C') {
// document.getElementById("input").setAttribute("value", "");
inputZone.value = "";
}
else if (value == 'ans') {
// document.getElementById("input").setAttribute("value", "");
//1.创建请求对象
const xhr = new XMLHttpRequest();
//2.配饰请求方法,设置GET请求接口地址
xhr.open('get', baseURL + "/api/storage");
//3.发送请求
xhr.send();
//4.网络请求返回的数据
// xhr.readystate===4代表响应完成了,xhr.status === 200 代表请求成功
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var res = xhr.responseText;
inputZone.value = res;
}
}
}
else {
let newVal = input + value;
inputZone.value = newVal;
}
}
//6.numSwitch() 对分离的操作数内部进行特殊计算
function numSwitch(num) {
let result = 1;
let regMatchSpecial = /sin|cos|√|log/;
let nums = num.split(regMatchSpecial);
let operators = num.match(regMatchSpecial);
if (nums[0] != '') { result *= parseFloat(nums[0]) }
for (let i = 1; i < nums.length; i++) {
if (nums[i] == '' || nums[i].split('.').length == 3 || nums[i].split(".")[0] == '') { return "error" }
switch (operators[i - 1]) {
case "sin":
result *= Math.sin(parseFloat(nums[i]) * Math.PI / 180)
break;
case "cos":
result *= Math.cos(parseFloat(nums[i]) * Math.PI / 180)
break;
case "√": {
result *= Math.sqrt(parseFloat(nums[i]))
break;
}
case "log": {
result *= Math.log(parseFloat(nums[i]))
break;
}
default:
return "error"
}
}
return result;
}
Firstly, I added corresponding symbols and other special symbolic operations on the basis of the original, such as exponents and complementary operations, etc. I added them to the two functions oper () and numSwitch().
Second, the number of buttons has changed from 20 to 25.
Then there are the two more important buttons, one is “=” and the other is the new “ans” button.
They relate to GET and POST requests in http requests, and need to store the result of the calculation in the form of a string in the back-end server, and can be read at any time.
I used the browser’s built-in api for sending httpRequest requests, or ajax requests.
Data is received and sent by changing the corresponding request message and identifying the server status code.
There is no need to explain the rest, because it is too simple.
Back-end
Project structure
Code rendering
I used Node.js and express to build the server this time.
The express package provides a number of powerful apis, also supports handling json and urlencoded content types, and its syntax is relatively concise.
Since only the most recent history was required for this assignment, I did not use databases such as MySQL and MongoDB because there was really no need to add code to store a single piece of data.
Let’s continue to look at the code, as usual, first:
npm i express
to install the package dependency of express.
The second is to solve the cross-domain problem, because I have a lot of relevant experience, so I took the method of adding cors request header to solve the browser same-origin policy.
Ok, now it’s a matter of sending and storing data.
According to the GET and POST requests from the front end, we write the corresponding interface and execute the corresponding callback function.
Then, for the POST request, I choose to save the record locally.
const express = require('express');
const fs = require('fs')
const serverApp = express();
var allowCors = function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
// 新增加cors 运行的请求头
res.header('Access-Control-Allow-Headers', 'Content-Type,lang,sfopenreferer ');
res.header('Access-Control-Allow-Credentials', 'true');
next();
};
serverApp.use(allowCors);//使用跨域中间件
serverApp.use(express.urlencoded());
serverApp.use(express.json());
serverApp.use(express.text());
var port = 8081;
function getLastInfo(res) {
fs.readFile('./Record.txt', 'utf8', (err, result) => {
if (err) { return console.log('历史记录读取失败!') }
res.send(result);
})
}
var addRecent = function (recordInfo) {
fs.writeFile('./Record.txt', recordInfo, (err) => {
if (err) {
return console.log('写入失败!')
}
console.log('计算结果写入成功!');
})
}
serverApp.get('/api/storage', (err, res) => {
getLastInfo(res);
})
serverApp.post('/api/addStorage', (req, res) => {
let recordInfo = req.body;
addRecent(recordInfo);
res.send('ok');
})
serverApp.listen(port, () => { console.log(`服务器开启成功!端口号:${port}.`) });
Feel free to contact me if you don’t understand.