前后端分离计算器系统(Flask + SQLite + HTML/CSS/JS)
Frontend/Backend Separated Calculator System — Assignment BlogTable of ContentsFrontend/Backend Separated Calculator System — Assignment Blog1. Course Information2. Git Repository Link and Code Standards Link3. PSP Table4. Presentation of the Finished Product4.1 Main Interface4.2 Addition4.3 Subtraction4.4 Multiplication4.5 Division4.6 Decimal Arithmetic4.7 Operator Precedence4.8 Parentheses4.9 Unary Minus4.10 Division by Zero4.11 Invalid Expression4.12 History Records4.13 Persistence4.14 Delete a Single Record4.15 Clear All4.16 Keyboard Input (Extended Feature)5. Design and Implementation Process5.1 Requirements Analysis5.2 Overall System Architecture5.3 Front-End Design5.4 Back-End Design5.5 API Design5.6 Database Design5.7 Expression Evaluation Algorithm Design5.8 Exception Handling5.9 Frontend/Backend Interaction Process5.10 Deployment Process6. Function Structure Diagram7. Code Explanation (Key Code Design Rationale)7.1 Backend: Expression Parsing (calc.py)7.2 Backend: REST Interface (app.py)7.3 Backend: History CRUD (app.py)7.4 Frontend: Unified Input for Keyboard and Buttons (app.js)7.5 Frontend: History Records (Proof of Persistence)8. Personal Journey and Learnings9. Extended Features (Extra Credit)10. Deployment / Access InformationBackend (public address)Frontend (public address)How to test1. Course InformationCourse for This AssignmentWeb Development TechnologyWeb 开发技术Assignment RequirementsImplement a calculator system with a frontend/backend separated architecture; the backend must do all calculation and persist history in a database; deploy the project and submit the blog, the two GitHub repositories and a publicly accessible address.Objectives of This AssignmentUnderstand the frontend/backend separation architecture, REST API design, database persistence, expression parsing, deployment, and write a complete assignment blog.Other ReferencesAssignment notice: https://bbs.csdn.net/topics/620530837ItemContentStudent王少杰Student ID832401219PlatformWindows Python 3.14 Flask SQLite HTML/CSS/JSDate2026-09-242. Git Repository Link and Code Standards LinkItemLinkFrontend repositoryhttps://github.com/060725/calculator_frontendFrontend code standardhttps://github.com/060725/calculator_frontend/blob/main/codestyle.mdBackend repositoryhttps://github.com/060725/calculator_backendBackend code standardhttps://github.com/060725/calculator_backend/blob/main/codestyle.md3. PSP TablePhaseEstimated (hours)Actual (hours)Requirements analysis0.50.5System design (architecture / API / database)0.50.5Backend: expression parsing calculation module1.52.0Backend: calculation history database module0.50.5Frontend: UI, interaction, keyboard shortcuts2.02.0Frontend/backend integration testing1.01.0Deployment (PythonAnywhere GitHub Pages)0.50.5Blog writing1.01.5Total7.58.54. Presentation of the Finished Product4.1 Main InterfaceThe page shows a dark-themed calculator: the button area is on the left and the history panel is on the right.4.2 AdditionClick128in sequence; the result shows20.4.3 SubtractionAfter clearing, enter15-7; the result shows8.4.4 MultiplicationAfter clearing, enter6×7; the result shows42.4.5 DivisionAfter clearing, enter20÷4; the result shows5.4.6 Decimal ArithmeticAfter clearing, enter3.142.86; the result shows6.4.7 Operator PrecedenceAfter clearing, enter12×3; the result shows7instead of9, proving that multiplication/division have higher precedence than addition/subtraction.4.8 ParenthesesAfter clearing, enter(12)×3; the result shows9, and parentheses have the correct precedence.4.9 Unary MinusAfter clearing, enter3×±2; the result shows-6. You can also type3*-2with the keyboard.4.10 Division by ZeroAfter clearing, enter5÷0; the UI shows a red message: “除数不能为零” (Division by zero is not allowed).4.11 Invalid ExpressionAfter clearing, type onlyand press; the UI shows “表达式无效” (Invalid expression).4.12 History RecordsAfter several calculations, the right panel lists the expressions, results and timestamps in reverse chronological order.4.13 PersistencePressF5to refresh the page; the history records still exist, which proves the data is persisted in the backend database.4.14 Delete a Single RecordClick the×at the top-right of a record; that record is removed from both the list and the database.4.15 Clear AllClick “清空全部” (Clear All) at the top of the panel and confirm; all records are removed.4.16 Keyboard Input (Extended Feature)Type12*3directly with the keyboard and pressEnter; it computes7correctly.Full keyboard support (digits, - * /,( ),Enter,Backspace,Escape) is anextended featurebeyond the basic requirements (see Section 9).5. Design and Implementation Process5.1 Requirements AnalysisThe assignment requires afrontend/backend separatedcalculator system:The frontend provides a graphical interface (dark theme) and supports both mouse clicks and keyboard input;The backend provides an expression-evaluation API and persists history records in a database (SQLite);History records support deleting a single record and clearing all; records must survive page refresh;It must support the four basic operations, decimals, parentheses with precedence, and unary plus/minus;Invalid expressions (e.g., division by zero, a bare operator) must produce friendly error messages;The project must be deployed to a publicly accessible address so that the teaching assistant can verify it.5.2 Overall System Architecture┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │ Browser (Frontend) │ │ Backend Service │ │ calculator_frontend │ HTTP │ calculator_backend │ │ │ ──────► │ │ │ index.html / style.css │ JSON │ app.py REST API │ │ app.js (Fetch calls the API) │ ◄────── │ calc.py Expression parser │ │ · Calculator buttons / keys │ │ SQLite History persistence │ │ · History panel / error msg │ │ (calculator.db) │ └─────────────────────────────────┘ └─────────────────────────────────┘The frontend and backend communicate through a REST API with JSON. The frontend nevertouches the database directly and never computes the result itself — the calculation isalways done on the backend. This is the essence of “frontend/backend separation”.A simple way to verify this: if the backend service is stopped, the frontend can stillaccept input but can no longer obtain any new valid calculation result.5.3 Front-End DesignSingle-page UI: left panel is the calculator button grid, right panel is the history list.Every button carries adata-keyattribute; clicks and keyboard events share one input channel.The display area has three lines: the input expression, the result, and a red error message.After a successful calculation the frontend re-queries the history API to refresh the panel.5.4 Back-End DesignFlask app exposing a small REST API (calculation history CRUD).A hand-writtenrecursive descent parser(calc.py) — noeval/execis used,which satisfies the assignment’s security requirement.CORS is enabled so an independently hosted frontend can call the API cross-origin.SQLite for persistence; awsgi.pyentry is provided for production deployment.5.5 API DesignMethodPathRequest Body / ParamsResponsePOST/api/calculate{expression:12×3}201:{id, expression, result, created_at}GET/api/history—{items:[{id, expression, result, created_at}]}DELETE/api/history/idpath param{ok:true}DELETE/api/history—{ok:true, deleted:n}(optional “clear all”)On success:201with the result, and the record is written to history;On failure (division by zero, invalid expression, etc.):400with a Chinese message in theerrorfield.5.6 Database DesignSQLite database filecalculator.dbwith a single tablehistory:FieldTypeDescriptionidINTEGER PRIMARY KEY AUTOINCREMENTPrimary keyexpressionTEXT NOT NULLThe expression evaluatedresultTEXT NOT NULLThe evaluation resultcreated_atTEXT NOT NULLRecord timeYYYY-MM-DD HH:MM:SSThe table is created automatically on first startup (init_db()), so no manualdatabase initialization is required. History is queried withORDER BY id DESC LIMIT 100,so the records remain visible after a page refresh.5.7 Expression Evaluation Algorithm DesignThe backend uses arecursive descent parser(calc.py):expr : term (( | -) term)* term : factor ((* | / | × | ÷) factor)* factor : ( | -) factor | ( expr ) | numberThe grammar naturally handles “multiplication/division before addition/subtraction”, parentheses, and unary plus/minus;Division by zero raisesValueError(除数不能为零)and a parse failure raisesValueError(表达式无效);Results are formatted uniformly:6.0 → 6,0.30000000000000004 → 0.3.Input is tokenised with a whitelist regex, so arbitrary code can never be executed.5.8 Exception HandlingBackend: business errors raiseValueErrorwith Chinese messages; the API layer mapsthem to400errorfield. Unexpected internal errors are caught and returned as500with a generic message.Frontend: on a non-2xx response, the red message area shows the backend’serrortext; if the backend is unreachable the UI shows “无法连接到后端服务”(Cannot connect to the backend service) instead of a wrong result.5.9 Frontend/Backend Interaction ProcessUser clicks a button / presses a key ↓ Frontend builds the expression string ↓ POST /api/calculate { expression: 12×3 } ↓ Backend validates → parses → calculates → saves to SQLite ↓ 201 { id, expression, result, created_at } ↓ Frontend shows the result and refreshes the history panel (GET /api/history)5.10 Deployment ProcessBackend: deployed withPythonAnywhere(free tier) using thewsgi.pyentrypoint; the Flask app runs behind PythonAnywhere’s web server.Frontend: deployed withGitHub Pages(free static hosting);chooses the production backend address when opened from the deployed domain.Online addresses and test instructions are listed inSection 10.6. Function Structure DiagramFrontend/Backend Separated Calculator System ├── Frontend calculator_frontend │ ├── Calculator UI (dark theme) │ │ ├── Digit / decimal point input │ │ ├── Four basic operator input │ │ ├── Parenthesis input │ │ ├── Sign toggle (±) │ │ ├── Clear (AC) / backspace │ │ └── Evaluate () │ ├── Keyboard shortcuts (extended) │ ├── Error messages (red, division by zero / invalid expression) │ └── History panel │ ├── Shows expression / result / timestamp │ ├── Delete a single record (×) │ └── Clear all (with confirmation) └── Backend calculator_backend ├── POST /api/calculate evaluate expression write history ├── GET /api/history read history ├── DELETE /api/history/id delete one history record ├── DELETE /api/history clear all history └── SQLite persistence7. Code Explanation (Key Code Design Rationale)7.1 Backend: Expression Parsing (calc.py)defparse_term(self):valueself.parse_factor()whileself.peek()in(*,/,×,÷):opself.take()rhsself.parse_factor()ifopin(/,÷):ifrhs0:raiseValueError(除数不能为零)# business error - HTTP 400value/rhselse:value*rhsreturnvalueDesign rationale: The grammar is a three-level recursionexpr → term → factor.Thetermlevel parses afactorfirst and then handles multiplication/division,so the multiplication/division “binds” tighter and naturally has higher precedencethan the addition/subtraction handled by theexprlevel. Thefactorlevel alsohandles parentheses and unary minus, covering cases like(12)×3and3×-2.Noeval/execis used anywhere — the input is parsed with a whitelist token regex.7.2 Backend: REST Interface (app.py)app.route(/api/calculate,methods[POST])defcalculate():expression(request.get_json(silentTrue)or{}).get(expression,).strip()try:resultevaluate(expression)exceptValueErrorasexc:returnjsonify({error:str(exc)}),400curconn.execute(INSERT INTO history (expression, result, created_at) VALUES (?, ?, ?),(expression,result,created_at))conn.commit()returnjsonify({id:cur.lastrowid,expression:expression,result:result,created_at:created_at}),201Design rationale: The endpoint only does “receive expression → validate → evaluate →save to DB → return”. Business errors are uniformly mapped to400 error message, which thefrontend renders as a red message. Parameterized SQL (?placeholders) prevents injection attacks.7.3 Backend: History CRUD (app.py)app.route(/api/history/int:rid,methods[DELETE])defdelete_record(rid):connget_conn()try:conn.execute(DELETE FROM history WHERE id ?,(rid,))conn.commit()finally:conn.close()returnjsonify({ok:True})Design rationale: Deletion goes through the backend API and removes the row from thedatabase for real; the frontend then re-queriesGET /api/historyto refresh the list,so the displayed data always reflects the latest state of the backend database.7.4 Frontend: Unified Input for Keyboard and Buttons (app.js)document.addEventListener(keydown,(event){constkeyevent.key;if(/[0-9]/.test(key))insert(key);elseif(key*)insert(×);elseif(key/){event.preventDefault();insert(÷);}elseif(keyEnter){event.preventDefault();evaluate();}elseif(keyBackspace)backspace();elseif(keyEscape)clearAllInput();});Design rationale: Every button carries adata-keyattribute, and both buttonclicks and keyboard events go through the sameinsert()input channel.*//areautomatically converted to×/÷before being sent to the backend, so clicking withthe mouse and typing with the keyboard behave identically (see screenshot 4.16).This keyboard support is one of the extended features.7.5 Frontend: History Records (Proof of Persistence)asyncfunctionloadHistory(){constresawaitfetch(${API_BASE_URL}/history);constdataawaitres.json();renderHistory(data.items||[]);}Design rationale: History is not stored inlocalStorage; it is read from thebackend SQLite database every time. Therefore the records survive a page refresh(F5), which demonstrates the frontend/backend separation idea that “data ispersisted by the backend” (see screenshot 4.13).8. Personal Journey and LearningsI truly understood frontend/backend separation: the frontend is onlyresponsible for display and interaction, while all calculation and data storageare delegated to the API. The two sides communicate via JSON with clearresponsibilities, so they can be developed and deployed independently.The recursive descent parsergave me a concrete understanding of therelationship between grammar and precedence. Before, I only knew the precedencerules; this time I implemented them with a grammar myself and realized that thelayering ofterm/factoris exactly where precedence comes from.Error handling must reach the frontend: aValueErrorraised in the backendhas to be converted into an HTTP status code plus a user-friendly message, and thefrontend renders it as a red error message — the full chain must be complete.SQLite made persistence painless: no separate database service needed, asingle file does the job. It fits course assignments perfectly and is more thanenough for the CRUD operations on history records.Deployment taught me the difference between local and online environments:the frontend and the backend live on different domains, so CORS must be enabledand the frontend must switch its API address automatically.Through the unified input channel of keyboard shortcuts anddata-key, I merged“clicking” and “typing” into one logic flow and learned the value of abstraction.9. Extended Features (Extra Credit)The following features go beyond the basic requirements and have been implemented anddemonstrated:FeatureDescriptionDemonstrationKeyboard shortcutsFull keyboard input: digits, - * /,( ),Enter,Backspace,EscapeScreenshot 4.16Delete a single history recordEach record has a×button; deletion is executed through the backend APIScreenshot 4.14Clear all history“清空全部” button with a confirmation dialogScreenshot 4.15Red error messagesDivision by zero / invalid expression shown in red, coming from the backendScreenshots 4.10, 4.1110. Deployment / Access InformationBackend (public address)https://060725.pythonanywhere.comFrontend (public address)https://060725.github.io/calculator_frontend/How to testOpen the frontend address in a browser (desktop or mobile).Click or type any expression, e.g.128, then press— the result20is returned by the backend.Check the history panel: records (expression / result / time) are stored in the backend database and surviveF5.Try5÷0— the red message “除数不能为零” appears; trythen— “表达式无效”.To verify separation: stop the backend and press— no new valid result can be computed.