Flask+Vue前后端分离:家庭设备维修服务系统实战

发布时间:2026/10/12 22:05:37
Flask+Vue前后端分离:家庭设备维修服务系统实战
做后端系统的朋友应该都有这种体会需求看着不难但等自己真正要搭一套能跑、能演示、能给别人看的完整项目时往往比想象中麻烦得多。今天拿一个家庭设备维修服务系统说事主技术栈走的是 python flask vue 这条经典路线整个项目保留了完整的源码和初始化脚本。系统本身解决的是一类很接地气的问题家里空调漏水、热水器打不着火、洗衣机异响用户不想翻电话本平台需要把报修信息、维修师傅和订单状态串起来统一管理。这套源码适合三类人参考一是在校学生拿来做课程设计或毕业设计二是想从单体脚本进阶到前后端分离项目的开发新人三是准备面试时需要一个完整项目作为谈资的求职者。它不是一堆积木式的 demo而是把用户、师傅、管理员三种角色都塞进了同一个平台逻辑闭环演示起来效果很直观。1. 项目整体设计与技术选型1.1 维修场景下的需求拆解家庭设备维修本质上是一个低频、但一旦发生就有时效要求的服务场景。用户发现设备异常第一反应是尽快找到能修的人维修师傅则希望少跑空路、多接有效单子。平台在这一过程中要充当三个角色信息收集者、任务分配者和过程监督者。基于这个定位系统核心需求可以拆成四条线用户线注册登录、提交报修单、填写设备类型与故障描述、填写联系地址和期望上门时间、查看处理进度、取消未受理的订单。师傅线查看待接工单、接单、填写维修结果、更新订单状态处理中、已完成、无法维修。管理线审核用户、分配或调整师傅、维护设备分类、查看整体订单数据。基础线登录鉴权、操作日志、接口统一返回、异常捕获与提示。把这一层想清楚再动手才不会写着写着发现页面不知道该放什么功能。很多新人拿到类似项目源码第一反应是看代码我的建议是先看需求文档和数据库设计代码只是需求的最终翻译。不同角色的权限边界划清楚后面的路由设计和接口校验就会轻松很多。1.2 为什么选择 Python Flask Vue这套技术组合不是性能最优解却是性价比和上手友好度都很高的方案。Flask 属于微框架做的事情很纯粹接收 HTTP 请求、调用业务逻辑、返回 JSON 响应。它的路由写法很直白用装饰器把 URL 和一个函数绑定这对刚接触后端的人帮助很大。Vue 则把页面上重复出现的部分抽象成组件一个“维修工单卡片”可以在列表页、详情页多处复用改一处全平台生效。用生活化类比来理解Flask 像一个自带基础工具的工具箱需要什么往里面加什么Django 则像一辆组装好的房车配置齐全但想改装得先了解整车结构。Vue 加上工程化工具后就像一套预制板材搭页面速度快拆改也方便。对于家庭设备维修系统这种页面数量不多、交互逻辑中等复杂度的场景这套组合是典型的高性价比选择。对比一下几种常见方案方案后端风格适合场景上手难度Flask Vue轻量灵活中小型管理系统、毕设项目低Django 模板全家桶内容管理、快速原型中Spring Boot Vue企业级大型业务系统高如果是纯内部工具或者管理后台Django 自带 Admin 也够用但如果要定制交互界面前后端分离的 Flask Vue 会更有发挥空间。源码里选择 Vue 3 组合式 API配合 Vite 构建开发体验和运行性能都比老旧的 Vue 2 方案好一截。1.3 源码结构与分层思想拿到源码先看目录。这套项目一般会把前后端拆成两个独立目录后端用backend或server前端用frontend或web。一个典型的 Flask 后端结构backend/ ├── app.py ├── config.py ├── models.py ├── requirements.txt ├── api/ │ ├── __init__.py │ ├── auth.py │ ├── order.py │ ├── user.py │ └── technician.py前端结构frontend/ ├── package.json ├── vite.config.js ├── src/ │ ├── main.js │ ├── App.vue │ ├── router/ │ ├── api/ │ ├── views/ │ ├── components/ │ └── store/分层原则一句话总结页面只管渲染接口只做通信模型只碰数据库。初学者最容易犯的错是把 SQL 查询直接写在前端组件里或者在后端路由里堆积各种页面的逻辑。分层清晰的最大红利是后期改页面不用动数据库结构换数据库不用动页面代码。源码之所以能作为教学材料复用就是因为目录结构和命名规范足够清晰别人接手时不需要靠猜。2. 设备维修系统后端拆解Flask 核心模块2.1 数据库表结构与模型关系后端的第一步是把数据结构定清楚。家庭设备维修服务系统里的核心数据对象有这么几个用户、师傅、维修订单、设备分类、状态流转记录。用 SQLAlchemy 定义模型大致长这样class RepairOrder(db.Model): __tablename__ repair_order id db.Column(db.Integer, primary_keyTrue, autoincrementTrue) order_no db.Column(db.String(32), uniqueTrue, nullableFalse) user_id db.Column(db.Integer, db.ForeignKey(user.id), nullableFalse) technician_id db.Column(db.Integer, db.ForeignKey(technician.id)) device_type db.Column(db.String(50)) fault_desc db.Column(db.Text) address db.Column(db.String(255)) status db.Column(db.String(20), defaultpending) create_time db.Column(db.DateTime, defaultdb.func.now()) update_time db.Column(db.DateTime, defaultdb.func.now(), onupdatedb.func.now())这里解释三个容易被忽略的点第一order_no不要用自增 id 直接当订单号暴露给用户。真实系统里订单号通常包含日期和随机串既防止别人通过遍历 id 抓数据也方便电话报修时核对。生成规则可以做成“日期 随机四位数字 用户 id 后四位”保证可读性的同时又不容易被猜到。第二status字段最好存字符串状态码而不是中文。前端显示“待接单”“维修中”“已完成”后端存pending、processing、finished需要展示时再做映射。一旦要加多语言或修改文案不用动数据库。第三状态流转别只更新一个字段。建议单独建一张order_status_log表记录每次变化包括操作人、旧状态、新状态、时间。维修纠纷发生时这就是最有力的证据链。很多源码demo为了省事忽略这张表但实际项目里它几乎是必须的。表与表之间的关系也值得想清楚一个用户可以有多个维修单一个维修单在某段时间内只对应一位主责师傅。外键约束保证数据不会出现“订单指向一个不存在用户”的情况。删除用户时也要考虑是级联删除订单还是保留订单仅标记用户失效后者明显更符合维修场景——订单数据是服务履历不该随便消失。2.2 核心接口设计与路由规划接口设计遵循一个原则请求方法表达动作URL 表达资源。用户提交报修单是新增资源用 POST查询自己的订单列表是读操作用 GET修改订单状态用 PUT删除异常订单用 DELETE。核心接口可以归纳成下面这张表方法路径功能权限POST/api/auth/register用户注册公开POST/api/auth/login登录获取 token公开GET/api/user/orders查询当前用户订单用户POST/api/order/create新建报修单用户PUT/api/order/status更新订单状态师傅/管理GET/api/technician/queue查询待接单列表师傅GET/api/admin/stats平台数据统计管理写一个创建订单的接口示例app.route(/api/order/create, methods[POST]) jwt_required() def create_order(): uid get_jwt_identity() data request.get_json() order RepairOrder( order_nogenerate_order_no(), user_iduid, device_typedata.get(device_type), fault_descdata.get(fault_desc), addressdata.get(address) ) db.session.add(order) db.session.commit() return jsonify({code: 0, msg: 提交成功, data: {order_id: order.id}})几个实操细节所有接口统一返回{code, msg, data}三段结构前端 axios 拦截器看到 code 非 0 就知道出错了不用每个页面重复处理异常request.get_json()拿的是请求体里的 JSON如果前端用了form-data提交就要换成request.form.get()commit 之后别立刻返回日志记录和后续通知可以放在事务成功后。2.3 登录鉴权与权限控制小型项目用 token 鉴权比 session 更符合前后端分离的习惯。流程是用户登录成功后后端签发一个包含用户 id 的 token前端保存到 localStorage每次请求在 header 里带Authorization: Bearer token后端经过校验后通过get_jwt_identity()拿到当前用户身份。密码存储必须使用哈希。Flask 可以直接用 Werkzeug 自带的generate_password_hash和check_password_hash不需要额外引库。把密码明文放在数据库里是这类项目最常见的红线问题尤其在毕设源码传播开之后隐患很大。权限控制通过装饰器实现例如只有管理员能访问的接口def admin_required(fn): wraps(fn) jwt_required() def wrapper(*args, **kwargs): uid get_jwt_identity() user User.query.get(uid) if not user or user.role ! admin: return jsonify({code: 403, msg: 无权限访问}), 403 return fn(*args, **kwargs) return wrapper这种写法把鉴权从业务代码里剥出去接口函数里只关心怎么处理请求不用反复验证“这个人是谁”。角色字段建议定义成user、technician、admin三档而不是一长串数字可读性和扩展性强得多。接到新需求时在装饰器层面就能完成大部分权限过滤。3. 前端 Vue 的实现思路与页面交互3.1 前端项目初始化与路由规划前端我用 Vite 创建 Vue 3 项目相比 Vue CLIVite 冷启动快、依赖安装干净对需要反复调试的学生项目更友好。安装命令很简单npm create vitelatest frontend -- --template vue cd frontend npm install npm install axios vue-router pinia把页面按角色拆开普通用户看到报修表单和历史工单师傅看到任务大厅和自己名下的工单管理员能额外进入数据看板。路由规划用一个三级结构就够了/login和/register是独立页面。/user/**是用户中心子页面有报修、工单列表、工单详情、个人信息。/tech/**是师傅工作台子页面有待接单列表、我的任务、维修记录。/admin/**是管理后台子页面有用户管理、分类管理、统计面板。路由定义里建议用组件懒加载也就是页面组件通过() import(...)引入这样首屏只加载当前页面需要的 JS 文件而不是把所有页面一次性打包进来。项目页面越多这种做法的收益越明显。3.2 报修工单功能的前后端对接维修服务系统的核心页面是报修表单和工单列表。先说表单设备类型用下拉选择故障描述用文本域地址用输入框再附一个期望上门日期。前端校验三条底线设备类型不能为空、故障描述至少 5 个字、地址不能为空。校验不过直接拦截不向后端发请求减少无意义的网络调用。提交逻辑代码示例const submitOrder async () { const form await formRef.value.validate() const { data } await http.post(/api/order/create, { device_type: form.deviceType, fault_desc: form.faultDesc, address: form.address }) if (data.code 0) { ElMessage.success(报修提交成功) router.push(/user/orders) } }这里有个容易踩的坑axios 默认会把 JS 对象序列化成 JSON 字符串但如果拦截器里改写了transformRequest或者用了qs.stringify后端接到的就不再是 JSONFlask 的request.get_json()就会返回None接口直接报 400。排查这类问题最快的办法是打开浏览器开发者工具的 Network 面板看请求头里的Content-Type和请求体的原始内容。工单列表页面除了展示数据还要把状态做成可读的标签。后端返回pending前端通过一个映射函数转成“待接单”这个方法比后端拼好中文再返回更好因为前端可以根据状态做差异化样式展示比如待接单黄色、维修中蓝色、已完成绿色、已取消灰色。状态多的时候也可以维护一个配置数组统一管理文案和颜色。3.3 路由守卫与全局状态管理登录状态不能只靠页面判断必须由路由统一把守。用 Vue Router 的全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else if (token to.path /login) { next(/) } else { next() } })用户角色信息用 Pinia 存储。页面刷新后store 会被清空需要重新从 localStorage 读取或者再请求一次/api/auth/profile获取用户信息。这一步如果不做刷新后页面会短暂变成“未登录”状态已登录用户再跳一次登录页体验很差。状态管理里只放和全局相关的东西token、用户基本信息、未读通知数量。单页内部的数据比如某个工单的详情保持组件局部状态就够了不需要全部都塞进 store。滥用全局状态会让调试变得很痛苦而且数据来源不清晰。4. 本地部署与项目运行全过程4.1 Python 环境与依赖安装本地把项目跑起来第一步是装 Python。建议用 3.8 以上的稳定版本比如 3.10 或 3.12。装完 Python 后创建一个虚拟环境避免系统全局环境被污染python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate pip install -r requirements.txtrequirements.txt建议优先锁定大版本不要全部用钉死否则换环境时容易因为版本冲突装不上Flask2.3.* Flask-SQLAlchemy3.0.* Flask-Cors4.0.* PyMySQL1.1.*如果本地只是演示数据库可以先用 SQLite一行配置都不用改。SQLite 是嵌入式数据库适合开发调试文件即库不用单独安装服务生产环境再切回 MySQL只需要改数据库连接 URL 和驱动。源码里如果能同时保留两种配置对使用者来说会非常省心。4.2 初始化数据库与启动后端服务初始化数据库的脚本一般放在init_db.py或者在 Flask shell 里执行flask shell from app import app from models import db with app.app_context(): ... db.create_all()如果系统有预置的分类数据比如“空调维修”“热水器维修”“洗衣机维修”还需要在初始化脚本里写入种子数据不然用户提交报修单时下拉框是空的演示效果直接打折。种子数据建议做成一个函数集中管理不要散落在各个页面代码里。后端启动python app.py开发模式下默认跑在http://localhost:5000。看到终端输出Running on http://127.0.0.1:5000就说明 Flask 已经起来了。此时可以直接用浏览器访问后端接口但注意前端页面还没有起来需要继续下一步。4.3 启动前端并打通前后端代理前端开启开发服务器npm run dev默认端口通常是 5173。前后端端口不同会触发浏览器的跨域拦截。开发阶段的解法是在 Vite 里配置代理让所有以/api开头的请求都转发到后端 5000 端口// vite.config.js server: { proxy: { /api: { target: http://localhost:5000, changeOrigin: true } } }这样前端代码里请求地址只写/api/order/create不用写完整 URL。做这个配置再配合 Flask-Cors开发机上的跨域问题基本不存在。生产环境部署时则需要由 Nginx 统一代理前端静态文件和后端接口把 80 端口收到的请求按路径分流这是另一个话题但源码里只要前后端分离结构清晰迁移到生产并不会太难。5. 常见问题与排查经验5.1 请求跨域被拦截最典型的现象前端页面能打开但点击登录时 Network 面板里请求标红浏览器控制台提示 CORS policy 相关报错。原因通常是后端没有允许前端的来源域名。解决思路是两条路同时走后端安装 Flask-Cors 并配置允许来源前端用 Vite 代理。只开后端 CORS 不开代理也行但代理的优势是规避生产环境部署时同域名下的复杂配置建议开发期用代理。5.2 JSON 参数与 Form 参数混淆前端提交数据时用 JSON 还是表单一定要和后端保持一致。用了 JSON后端就用request.get_json()用了application/x-www-form-urlencoded后端就用request.form.get()。源码里如果两类接口混用排查起来非常费神。建议全项目统一使用 JSON 数据格式只在文件上传场景切换成 FormData。5.3 中文乱码问题中文乱码基本围绕两个位置出现。一是从数据库读出来乱码二是接口返回 JSON 里的中文被转成了\uXXXX。前者要检查数据库连接的 URL 是否带charsetutf8同时建表时确认字符集是 utf8mb4后者是因为 Flask 的jsonify默认开启ensure_ascii把中文转成了 unicode 转义。解决方式是把app.config[JSON_AS_ASCII] False配置加进去这样返回的就是可读的中文。5.4 依赖版本冲突Flask-SQLAlchemy 3.0 和 2.x 在很多写法上有区别典型的是db.ForeignKey可以直接在列里使用字符串引用而不是必须传模型类。网上很多教程是 2.x 时代的按老写法抄到新版本可能报TypeError。项目源码的requirements.txt如果锁定了版本安装时尽量不要随意升级特别是flask和sqlalchemy两个核心库。遇到版本报错时宁可在虚拟环境里重装指定版本也不要全局乱改。5.5 页面刷新后登录状态丢失前面提过 Pinia 状态在刷新后会清空实际表现是用户操作到一半刷新页面直接弹回登录页。解决办法是在入口文件main.js或 App 组件的created生命周期里检测到 localStorage 里有 token 就重新获取用户信息并写入 store。另外路由守卫里要区分“未登录需要跳转”和“正在恢复登录状态中”避免因为初始化还没完成就误判。整理一下常见问题的排查优先级现象优先检查位置常用解决方案请求被 CORS 拦截后端 Flask-Cors 配置 / 前端 proxy加配置或改代理接口 400请求头的 Content-Type统一 JSON 格式中文乱码或转义数据库字符集 / JSON_AS_ASCIIutf8mb4 关 ensure_ascii刷新丢登录Pinia 初始化逻辑入口处恢复用户信息这套家庭设备维修服务系统从技术含量上说并不是什么高深项目但程序员日常的工作恰恰就是通过组合 python、flask、vue 这类成熟工具把真实业务落地成可用的系统。我个人在复现这类源码时最深的体会是判断一个项目源码好不好先看它有没有把“状态变化”想清楚——用户提交、师傅接单、维修完成每一步数据的流向和权限控制是否闭合。后面复盘时别忘了先把初始化脚本跑通完成一次“用户注册—提交报修—师傅接单—刷新列表”的完整手动测试对整个系统的理解会瞬间清晰很多。这个系统后续要扩展方向也很明确接入地图选点定位上门地址、增加师傅的评价打分体系、加一个简单的统计报表模块。每加一个功能都绕不开今天的这些基础设计所以把地基打扎实比什么都重要。