news 2026/10/2 18:57:03

3个综合练习项目:命令行记账本、待办事项应用与FastAPI接口实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
3个综合练习项目:命令行记账本、待办事项应用与FastAPI接口实战

编程练习这件事,我见过太多人卡在同一个地方:语法都懂,小例子都会,一碰到“把功能串起来”就不知道从哪里下手。3个综合练习题目,就是专门用来破这个局的。它不是一个知识点配一个demo,而是把文件操作、数据解析、交互逻辑、接口设计这些零散内容揉到三个完整可运行的小项目里,让练习者在真实场景里把“学过的东西”变成“会用的技能”。这篇文章我会按难度递进逐个拆解:命令行记账本、浏览器待办事项应用、轻量级待办事项API。每个题目都讲清楚为什么这么设计、核心怎么写、哪些坑我帮你先踩过,按这个顺序练下来,你会明显感觉到自己的代码组织能力不一样了。

1. 练习题目设计的整体思路

1.1 为什么一定要选“能完整跑起来”的题目

很多人练编程喜欢挑那种“算法题”,比如反转链表、排序数组。不是说算法没用,而是如果只练算法,你会发现真的要去写一个能交给用户的东西时,还是两眼一抹黑。综合练习题目和算法题最大的区别在于:它逼着你考虑数据怎么存、界面怎么交互、出错怎么办、以后怎么扩展。这才是真实开发里消耗精力最多的地方。命令行记账本看起来很简单,但你要处理文件读写、日期格式、金额校验、统计逻辑;待办事项应用看似只是个列表,但你要考虑状态同步、DOM更新、浏览器存储;待办API看似只要几个接口,但你要设计数据模型、异常处理、测试方式。这些恰恰是从“会写代码”到“能交付代码”之间的关键差距。

这里有个我一直坚持的看法:练综合项目,代码量并不是第一目标,把每个功能模块“咬合”在一起的能力才是。你写一个函数和写一个系统,区别不在函数本身,而在函数之间的边界和调用关系。这三个题目正好覆盖了三种最常见的程序形态:本地命令行工具、纯前端交互页面、后端接口服务。练完这一个组合,你对“程序从输入到输出、从存储到展示”的完整链路会有很具体的体感,而不是停留在抽象概念上。

1.2 三个题目的难度阶梯与能力对照

三个题目不是随便凑的,我按难度和依赖方向做了仔细划分。命令行记账本只用Python标准库,不装任何第三方包,入门成本最低,适合刚学完基础语法、想练文件读写的人。待办事项应用只用HTML、CSS、JavaScript,不碰框架,练的是纯浏览器环境下的DOM操作与本地存储。待办API用FastAPI配合Pydantic,会引入框架思维、REST风格、接口测试这些概念,对想往后端方向发展的人特别有帮助。

我把三个题目的核心能力对应关系放在下面,你可以对照自己的情况选择练习重点:

题目主要编程语言/技术核心能力训练点适合基础
命令行记账本Python 标准库函数拆分、JSON读写、命令行交互、异常处理掌握基础语法即可
待办事项应用JavaScript + 浏览器APIDOM操作、事件绑定、localStorage、状态同步学过JS基本语法
待办事项APIPython + FastAPI接口设计、数据校验、HTTP方法、自动化测试了解Python、想入门后端

这样安排还有一个好处:三个项目之间不需要互相依赖,你单独练哪一个都可以。但如果按顺序练,前面的基本功会在后面直接复用。比如你在记账本里练过的JSON序列化和反序列化,到了API项目里就是请求体和响应模型的雏形;你在待办事项应用里练过的“新增数据、刷新视图”逻辑,到了API项目里就变成了Create和Read两个接口。技能之间是互相借力的。

2. 第一题:命令行记账本

2.1 需求定义与功能拆分

我见过很多记账本教程上来就让你写GUI或者网页版,这其实是把简单问题复杂化了。命令行记账本的核心价值不在界面,而在于“记录”和“统计”这两个动作。我给它的需求定得很朴素:支持记录一笔收入或支出,支持按类别查询,支持按月汇总,数据持久化保存到本地文件,不依赖第三方库。

为了让项目边界清晰,我把功能拆成三个模块:数据层负责读文件、写文件;业务层负责新增记录、筛选、汇总;交互层负责读取用户输入并调用业务层。具体来说,数据层用一个JSON文件当作“数据库”,字段结构我建议固定为amount、category、note、date四项。amount统一用浮点数,正数代表收入,负数代表支出;date用YYYY-MM-DD的字符串保存,方便后续比较和按月聚合。业务层里最核心的是add_transaction和monthly_summary两个函数,前者把一条记录追加到列表里,后者遍历所有记录,按年月分组求和。

这样拆完以后,你会发现哪怕后面要升级成SQLite存储或者Web界面,数据层和业务层都可以复用,只需要替换交互层。这就是模块化的价值。很多新手写这类功能时会习惯性地把所有代码堆在主循环里,短时间能跑,但一旦要加统计功能,主循环会膨胀得没法维护。我建议从一开始就养成“函数只做一件事”的习惯,每个函数控制在20行以内,主流程只负责调度。

2.2 核心实现:JSON文件读写与命令行交互

数据层我用json模块实现,但有一点值得注意:json.dump默认会把数据压成一行,中文也会被转义成\uXXXX,这对排查数据问题很不友好。所以我保存时会强制加两个参数:ensure_ascii=False让中文正常显示,indent=2让数据按层级缩进。这样你打开JSON文件就能直接看到有没有脏数据。

import json from datetime import datetime DATA_FILE = "ledger.json" def load_data(): try: with open(DATA_FILE, "r", encoding="utf-8") as f: return json.load(f) except FileNotFoundError: return {"transactions": []} def save_data(data): with open(DATA_FILE, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) def add_transaction(data, amount, category, note=""): data["transactions"].append({ "amount": float(amount), "category": category, "note": note, "date": datetime.now().strftime("%Y-%m-%d") })

这里我故意用try...except FileNotFoundError而不是直接判断文件是否存在,是因为在并发或异常场景下,“先判断后读取”容易出现竞态问题。对新手来说,直接捕获异常更省心,逻辑上也更贴近“文件不存在就当空数据用”这个语义。菜单部分我用一个while True死循环包住input(),用户输入数字选择功能,输入q退出。注意input()拿到的永远是字符串,转float时要做校验,否则用户输入abc程序会直接崩溃。

def main(): data = load_data() while True: print("\n1. 记一笔 2. 本月汇总 3. 查看全部 4. 退出") choice = input("请选择:").strip() if choice == "1": try: amount = float(input("金额(收入正数,支出负数):")) category = input("类别:").strip() note = input("备注(可跳过):").strip() add_transaction(data, amount, category, note) save_data(data) print("已保存") except ValueError: print("金额格式不正确,本次操作已取消") elif choice == "2": monthly_summary(data) elif choice == "3": show_all(data) elif choice == "4": break else: print("无效选项,请重新输入")

执行上面的代码后,你会得到一个可以反复使用的记账工具。每次运行都会读取ledger.json,所有记录持久保存。月汇总函数我建议按date[:7]取字符串前7位作为“年-月”分组键,这个技巧比用datetime.strptime拆解更直接,也避免时区等隐性坑。你还可以在汇总时顺手统计一下总收入、总支出和结余,给这个练习多增加一点“可用性”。

2.3 这个练习里最容易被忽略的细节

第一个坑是路径问题。很多人把文件名直接写成ledger.json,程序跑起来没问题,但当你换一个目录启动脚本时,文件会在新目录下重新创建,数据就“丢”了。我建议用pathlib.Path(__file__).parent拼出脚本所在目录,让数据文件始终和脚本在一起。这个细节虽然小,但能帮你理解相对路径和绝对路径的区别。你可以这样改:

from pathlib import Path BASE_DIR = Path(__file__).parent DATA_FILE = BASE_DIR / "ledger.json"

第二个坑是金额精度。记账本里我用float,但如果你要算利息或者做更严谨的财务统计,浮点数会产生类似0.1 + 0.2 = 0.30000000000000004的问题。练完基础版之后,可以把金额改成用Decimal或者直接用整数分来存储,这是一个很好的进阶方向。用整数分要注意的是,用户输入12.34时你要换算成1234,展示时再除以100,这样所有的加减运算都是整数运算,不会有精度损失。

第三个坑是交互层的校验。我在上面代码里处理了ValueError,但用户还可能输入空字符串、超长备注、不存在菜单编号。实用主义一点,先兜住最会发生的崩溃,不要追求把所有输入都校验一遍。真实项目里也是如此:优先保证主流程稳定,再把边界问题逐个补上。如果后续扩展成多用户版本,你还需要考虑并发写入同一个文件的问题,那时候可以引入threading.Lock或改用SQLite。

3. 第二题:浏览器待办事项应用

3.1 页面结构与核心状态设计

待办事项应用是前端练习里最经典的一道题,因为它麻雀虽小,五脏俱全:有表单输入、有列表渲染、有状态切换、有本地存储,翻来覆去能玩出各种花样。我的要求是不用任何框架,只用原生JavaScript实现。这样做的好处是你能真正理解document、localStorage、事件冒泡这些底层机制,而不是被框架抹平。很多同学在写完这个项目之后再去看React的useState,会有一种恍然大悟的感觉。

页面结构我建议保持极简,就是一个输入框、一个添加按钮、一个任务列表,再加三个筛选按钮。HTML骨架大概是这样:

<input id="todo-input" placeholder="输入新任务" /> <button id="add-btn">添加</button> <div> <button>const input = document.querySelector('#todo-input'); const addBtn = document.querySelector('#add-btn'); const list = document.querySelector('#todo-list'); let todos = JSON.parse(localStorage.getItem('todos')) || []; let currentFilter = 'all'; function save() { localStorage.setItem('todos', JSON.stringify(todos)); } function render() { const filtered = todos.filter(todo => { if (currentFilter === 'active') return !todo.completed; if (currentFilter === 'completed') return todo.completed; return true; }); list.innerHTML = ''; filtered.forEach(todo => { const li = document.createElement('li'); li.textContent = todo.title; li.className = todo.completed ? 'completed' : ''; li.dataset.id = todo.id; list.appendChild(li); }); } function addTodo() { const title = input.value.trim(); if (!title) return; todos.push({ id: Date.now(), title, completed: false }); save(); render(); input.value = ''; input.focus(); } addBtn.addEventListener('click', addTodo); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') addTodo(); }); list.addEventListener('click', (e) => { const id = Number(e.target.dataset.id); const todo = todos.find(t => t.id === id); if (!todo) return; todo.completed = !todo.completed; save(); render(); });

localStorage只能存字符串,所以每次保存时用JSON.stringify,每次读取时用JSON.parse,这两个操作会贯穿整个项目。第一次做这个练习的人经常会忘记在页面刷新后把数组重新载入,所以我会在开头写JSON.parse(localStorage.getItem('todos')) || [],用空数组兜底。注意JSON.parse是有可能抛异常的,如果之前存的数据被手动改坏了,页面会白屏。进阶版本可以用try...catch包一层,解析失败就重置为空数组。

筛选功能我建议用一个currentFilter变量保存当前状态,渲染时先根据它过滤todos,再生成列表。这样代码写起来最简洁,也顺带练到数组的filter方法。很多初学者会把筛选逻辑写在模板字符串里,用三元表达式一层层嵌套,那样可读性会非常差,不如单独用filter函数处理。筛选按钮的事件可以统一绑定,读取>from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import Optional app = FastAPI() class TodoCreate(BaseModel): title: str completed: bool = False class Todo(TodoCreate): id: int

TodoCreate是客户端传进来的结构,只包含title和completed;Todo是在存储时额外加上id的完整结构。把“创建时的输入”和“返回时的输出”分成两个模型,是API设计里非常重要的习惯。它能让接口的约束更清楚:客户端无法自己指定id,服务端也不会把内部字段意外暴露出去。如果你直接把TodoCreate当响应模型用,会发现响应里永远没有id,前端根本不知道怎么定位资源。这就是模型拆分不彻底导致的直接问题。

4.2 用FastAPI把核心接口写出来

为了保持示例简单,我直接用内存列表存储,不接数据库。这样练的是接口逻辑,而不是数据库用法。如果你已经学过SQLite或者MySQL,可以把它替换成数据库操作;如果没学过,先把内存版跑通,再逐步加持久化。内存版的缺点很明确:服务一重启数据全部消失,但这正好是一个天然的练习切入点——逼着你去了解SQLite。

from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() class TodoCreate(BaseModel): title: str completed: bool = False class Todo(TodoCreate): id: int todos = [] next_id = 1 @app.get("/todos", response_model=list[Todo]) def list_todos(): return todos @app.post("/todos", response_model=Todo, status_code=201) def create_todo(payload: TodoCreate): global next_id todo = Todo(id=next_id, **payload.model_dump()) todos.append(todo) next_id += 1 return todo @app.delete("/todos/{todo_id}", status_code=204) def delete_todo(todo_id: int): for i, todo in enumerate(todos): if todo.id == todo_id: todos.pop(i) return raise HTTPException(status_code=404, detail="任务不存在")

这里有几个关键点值得展开。response_model参数指定了响应结构,FastAPI会自动过滤掉不属于模型的字段,也自动把Python对象序列化成JSON。status_code=201表示创建成功,204表示删除成功且无响应体,这些状态码语义是后面写真实API时绕不开的基础功。payload.model_dump()是Pydantic v2的写法,如果你用的是旧版本,可能看到的是payload.dict(),这两个在新旧版本里都不算错,但要注意别混用。

删除接口当时我故意处理了“任务不存在”的情况,返回404。这个细节很多人会漏掉:接口不能只考虑正常路径,还要考虑异常路径。写完核心接口后,我强烈建议你把uvicorn跑起来,用curl或者Postman发几个请求验证,重点看三种场景:正常创建、删除一个不存在的id、POST时缺少title字段。第三种会得到422 Unprocessable Entity响应,那是FastAPI自动完成的字段校验,能让你真切感受到“框架帮你省了多少事”。

uvicorn main:app --reload

启动后打开http://127.0.0.1:8000/docs,你可以直接在浏览器里调试接口。这时候你可能会发现,调试接口比调试命令行程序更直观,因为每个请求的输入输出都是透明的。这个“可见性”正是前后端分离架构的优势之一:前端和后端可以并行开发,只要接口约定不变。

4.3 自动测试与联调技巧

后端代码最怕的是“手动点一遍没问题,一改就崩”。所以我建议从写接口的第一天就开始配测试。FastAPI搭配httpx和pytest可以非常轻量地做接口测试,不需要启动真正的服务器:

from fastapi.testclient import TestClient from main import app client = TestClient(app) def test_create_todo(): resp = client.post("/todos", json={"title": "写测试"}) assert resp.status_code == 201 assert resp.json()["title"] == "写测试" def test_delete_missing_todo(): resp = client.delete("/todos/999") assert resp.status_code == 404

这种测试跑在内存里,速度极快。一开始写测试可能觉得麻烦,但等你给接口加上鉴权、分页、数据库之后,测试的价值会翻倍增长。自动化测试本质上是在给你的接口“上保险”,每改一次代码就跑一遍测试,比手动重复验证省太多时间。再提一个我在联调时踩过的坑:前端用浏览器直接访问后端接口时,如果前端跑在http://localhost:8080而接口跑在http://localhost:8000,跨域请求会被浏览器拦截。FastAPI里可以用CORSMiddleware统一处理,加一行配置就能放开跨域限制。这个坑不是逻辑错误,但没遇到过的人可能会在浏览器控制台看到报错后一脸懵。

5. 常见问题与避坑实录

5.1 练习中最容易翻车的四个场景

我陪练过不少学员,也看过很多自己提交的代码,发现大部分问题其实集中在四个点上。

第一个是编码问题。Python读取或写入包含中文的JSON文件时,官方默认编码在不同平台上不一致,Windows尤其容易报UnicodeDecodeError。解决方案就是在open()里统一指定encoding="utf-8",同时保证文件本身确实是UTF-8编码保存。很多新手写完代码本地跑得好好的,在别人机器上一跑就乱码,几乎都是编码没锁死。这个坑在记账本和API项目里都会出现,尽早养成写编码参数的习惯能省掉大量排查时间。

第二个是路径问题。待办事项应用里,有人喜欢把localStorage理解成“数据库”,但它本质上只是浏览器里的键值存储,容量有限且随时可能被清掉。更关键的是,如果你用file://协议直接打开HTML页面,有些浏览器对localStorage的行为和服务器环境下并不完全一致。练习时建议用VS Code的Live Server或者python -m http.server起一个本地静态服务,避免文件协议带来的古怪问题。你还可以打开浏览器控制台,在Application面板里直接查看和清除localStorage,这对调试非常有用。

第三个是接口参数错误。FastAPI会根据类型注解自动解析请求,但新手经常分不清路径参数和查询参数。比如/todos/{todo_id}里,todo_id是路径参数;/todos?completed=true里,completed是查询参数。如果混用,会得到422或404。解决办法是在定义接口时始终问自己:这个id是不是资源定位的一部分?如果是,放路径;如果不是,是筛选项,放查询参数。还有一个相关问题是POST和PUT的区别:POST通常用于新增,PUT用于整体更新,很多人为了省事全用POST,结果接口语义越来越混乱。

第四个是数据持久化的缺失。内存数组保存数据,服务一重启就全丢了。练习时可以理解,但最好在完成后升级成文件存储或者SQLite,这会让项目的完整度上一个台阶。我见过很多人练完后只记得“接口能调通”,却忘了数据到底存哪了,这是非常危险的认知盲区。给API项目加上SQLite存储是个性价比很高的升级:表结构很简单,只有id、title、completed三个字段,却能让你接触到数据库连接、参数绑定、事务这些真正的后端基本功。

5.2 三个题目的边界与加分项

把上面三个题目都跑通之后,你容易产生一种“我都会了”的错觉。实际上,每个题目都还有不少边界没有覆盖到,把这些边界补上,才是拉开差距的地方。

记账本目前只处理了单用户单文件,你可以试着让程序支持多个账本文件,或者增加导出CSV的功能。CSV用csv模块就能写,用Excel打开后还能做更多统计,这个功能在实际使用中的价值很高。待办事项应用目前没有编辑功能,你可以给每个任务加上“双击修改标题”的交互,这会用到contenteditable或弹窗输入,顺便练一下内联编辑的焦点管理。最难的是任务拖拽排序,需要处理dragstart、dragover、drop这一组事件,能把排序逻辑理顺,你对浏览器事件机制的理解会上一个层次。

API项目目前只有增和查,你可以把更新接口PUT /todos/{id}补上,顺便练习怎么在路径参数和请求体同时存在时做校验。再加上分页参数limit和offset,就是一个小型真实接口了。分页看起来简单,但你要考虑limit为负数、offset超过数据长度这些边界情况,这些才是后端的常态。如果你还想继续深入,可以试着给接口加上简单的日志记录,每次请求把method、path、status_code写到一个文件里,这能让你直观地看到服务的运行状态。

5.3 关于代码组织和个人习惯的小建议

我经常看到有人把三个题目写完就丢在文件夹里吃灰,这很可惜。综合练习的意义不只是当时练一遍,更是要留下给你自己“回头看”的素材。我建议你在每个项目里写一个简短的README,把需求描述清楚,把怎么运行写明白,再把当时遇到的坑记下来。过两周再打开来看,你会发现很多当时觉得理所当然的设计,现在已经看出问题了——这就是成长。

我给学员批改代码时最看重的不是功能实现,而是函数和函数之间有没有清晰的分工。你可以在自己的代码上做一次“抽取练习”:把所有超过30行的函数拆成若干小函数,给每个函数写一行注释说明职责。这个过程很像整理房间,刚开始觉得没必要,但整理完后你很难再忍受乱糟糟的代码。每个小函数最好只做一件事,参数控制在三个以内,超过三个就考虑封装成一个对象或字典。我见过无数人栽在不命名上:变量叫data、temp、x,思路再清晰别人也看不懂。养成好命名习惯,对你的长期帮助比多写几个功能大得多。

提示:如果你是初学者,练这三个题目时不要强求一次写完。每完成一个功能就跑一下、提交一次,哪怕只是“能用就好”。代码是一步步长出来的,不是一下笔就完美的。

6. 一个完整的实操验收清单

结尾部分,我给一个可以直接照着打勾的验收清单。这个清单是我在实际带项目时总结出来的,用来判断“这个练习是否真的完成了”。你可以逐条对照,如果所有项目都能打勾,你已经远远超出了“能跑就行”的水平。

  • 记账本在无文件环境下第一次运行不会崩溃,能自动初始化数据文件
  • 记账本输入非法金额只会提示错误,不会中断主流程
  • 记账本可以按月汇总收入和支出,结果不会出现浮点数精度问题
  • 待办事项应用刷新页面后数据不丢失,任务状态还能正确显示
  • 待办事项应用支持筛选全部、未完成、已完成,且筛选后列表与状态一致
  • 待办事项应用新增、切换、删除操作后,localStorage里的数据同步更新
  • API服务有独立的请求模型和响应模型,客户端无法伪造id
  • API服务对合法请求返回正确的状态码,对非法请求返回422或404
  • API服务可以用自动化测试覆盖创建和删除的正常路径与异常路径
  • 所有项目都有README,说明运行方式和核心文件结构

我在实际陪练过程中最大的体会是:三个综合练习题目看起来不大,但它们共同构成了一个从“会用语法”到“能写小型系统”的最小训练闭环。很多人觉得项目必须大、必须商用才有练习价值,其实恰恰相反,小项目里能把边界想清楚、能完整跑通一遍,比半吊子做大项目有用得多。当你把最后一个API测试跑通时,回头看第一个记账本,你会明显看到自己理解深度的变化。如果你练完发现某个环节卡住了,优先回到最基础的那一步去排查——是不是JSON没存进去,是不是网络请求没发出去,是不是数据没渲染出来。这三个问题覆盖了数据层、传输层、视图层,你亲手把它们打通一次,后面再遇到什么框架、什么工具,都会觉得心里有底。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 18:56:54

conda管理R语言环境:依赖隔离与可复现实战

1. 前言&#xff1a;R 语言环境管理的真实痛点做数据分析和统计建模的人&#xff0c;大概率都经历过这样的场景&#xff1a;半年前跑通的一段脚本&#xff0c;今天换台机器重新运行&#xff0c;library()的时候直接报错说某个包版本不兼容&#xff1b;或者团队里三个人的分析结…

作者头像 李华
网站建设 2026/10/2 18:55:26

Spring Boot健身管理APP毕设项目:从技术选型到源码二次加工

健身管理类App在毕业设计里一直是热门选题&#xff0c;我身边不少带毕设的老师都反馈这类题目“好讲清楚、技术覆盖全、演示效果好”。这个题目看起来直接&#xff0c;但真做起来&#xff0c;涉及用户端、管理端、预约流程、数据统计、移动端展示等一系列环节&#xff0c;不是随…

作者头像 李华
网站建设 2026/10/2 18:55:03

AI编程落地一年:模型之外,流程、审查与合规才是成败关键

1. 一年的推进经历&#xff1a;从"做个Demo证明自己"到"全员铺开"的认知反转先交代一下背景。我在一家中型科技公司做研发效能相关的工作&#xff0c;就是那种"不上线业务功能、专管大家怎么写代码"的岗位。去年年初&#xff0c;领导拍板要推AI编…

作者头像 李华
网站建设 2026/10/2 18:54:20

网安复试编程Day19:进制转换、异或加密与IPv4校验实战

杭电网安复试编程准备到第19天&#xff0c;我总算把那些"看着简单、上手就错"的基础题折腾明白了。如果你也在准备杭电网安复试编程&#xff0c;或者正在纠结网安方向机试到底会考什么&#xff0c;这篇记录应该能给你一个比较具体的参考——我会把Day19这一天的完整练…

作者头像 李华
网站建设 2026/10/2 18:52:31

螺旋桨BEMT性能计算:基于Matlab的拉力、功率与效率分析

这件事我琢磨了好一阵子&#xff1a;螺旋桨这东西&#xff0c;从外面看就是个一转就飞的“大风扇”&#xff0c;但真要把它的拉力、扭矩、效率随飞行状态的变化规律算明白&#xff0c;牵涉到的流体力学细节足够让初学者挠头。我最初接触叶片单元动量理论&#xff08;Blade Elem…

作者头像 李华
网站建设 2026/10/2 18:51:49

SpringBoot+Vue3开发喀什旅游网站:前后端分离实战与踩坑记录

1. 项目背景与功能定位&#xff1a;这个喀什旅游网站到底要做什么 做这个项目的起因其实挺实际——喀什本地一家文旅公司想做自己的线上展示与预订平台&#xff0c;不想再依赖第三方OTA平台。需求本身不复杂&#xff1a;游客端能浏览景点介绍、查看旅游攻略、在线预订门票&…

作者头像 李华