ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python与Vue3的家电维修店管理系统开发实战与踩坑总结

基于Python与Vue3的家电维修店管理系统开发实战与踩坑总结 前阵子帮朋友的一家家电维修店做了一套管理系统后端用 Python前端用 Vue3整个项目就是标题里这个python-vue3家用电器家电维修店的管理系统。做之前我专门去他店里蹲了两天发现他们还在用纸质手写单加一个老掉牙的 Excel 记账本高峰期一天二三十单漏单、找单、记错型号的事经常发生。所以这个系统的目标特别朴素让前台小妹能快速录入维修单让维修师傅能看到今天有哪些活让老板每晚能对得上账。这套系统最终覆盖了客户档案、维修工单、派单、配件库存、收款记录、保修期跟踪这几块核心业务前端是 Vue3 Element Plus后端是 FastAPI数据库用的 PostgreSQL小规模可以换 SQLite。如果你正好也在做类似的全栈项目——不管是为了自己开店用还是做课程设计和毕业设计这篇文章里的设计思路和踩坑记录应该都能帮你少走不少弯路。1. 为什么维修店老板需要一套管理系统从纸质台账到数字化1.1 维修店日常管理最头疼的几件事先说一个很反直觉的事实绝大多数家电维修店不是因为没有系统才乱而是因为根本没有意识到自己每天在处理的数据量已经超出人工台账的承受范围了。一个小店每天二十张维修单看起来不多但每张单子背后连着客户电话、送修品牌型号、故障描述、检修结果、更换配件、维修费用、是否在保修期这一串信息。一周就一百四十单一个月五六百单。纸质的单子一旦堆起来想查一个客户两周前修的冰箱是什么故障得翻半天更别提多人同时登记信息口径不统一这种事。我在店里蹲点时发现三个最具体的问题客户来报修店员问完电话和故障就写在便利贴上单子随手一贴忙起来就找不到了。师傅修完机器口头跟柜台说换了什么配件柜台再补记到 Excel 里换配件这个动作经常漏记导致月底库存对不上。老板最想知道的是这个月赚了多少、哪些师傅产值最高、哪个配件用得最多但Excel里的数据长什么样完全取决于当时谁输入的没有任何约束。1.2 这套系统到底解决了什么问题所以这个系统不是一个看起来很酷的科技项目它要解决的就是信息录入规范化、查询方便、流程可追踪这三个问题客户信息录入后永久保存下次来电直接按手机号检索不用再问一遍您家地址是哪里。维修单有明确的状态流转客户打电话问进度店员打开电脑一秒钟就能看到是在检修中还是待取件。配件的入库、出库、挂账全部在系统里操作月底盘库的时候账面数量能跟实际数量对得上。另外还有一个往往被忽略的价值数据沉淀。系统跑半年以后哪个品牌返修率高、哪个型号容易出什么问题、哪个师傅的平均修时最短全都有数据支撑。虽然小店不搞什么大数据分析但老板至少知道下个月该多进什么配件这比拍脑袋订货靠谱得多。1.3 明确了需求再去选技术栈而不是反过来很多人在做类似项目的时候第一反应是我要做一个系统然后就开始纠结用 Python 还是 Java、用 Vue2 还是 Vue3。我的建议是反过来先把业务流程摸清楚把每张单子上的字段列出来再考虑用什么工具去实现。比如维修单这个核心实体里面的字段几乎就是纸面单据的电子化客户信息、送修品类、品牌型号、机身编码、故障现象、维修状态、维修师傅、更换配件明细、收费金额、取件时间。当你把这些字段一条条列出来整个系统的表结构基本就定了个七七八八后面写代码只是把这些信息从表单搬到数据库再展示出来而已。2. Python Vue3 的组合逻辑与项目骨架搭建2.1 为什么后端选 Python、前端选 Vue3技术选型这件事最怕的不是选得不好而是选了超出业务需要的重家伙。我最后敲定 Python 做后端Vue3 做前端理由很实际。后端用 Python开发效率高生态里处理 Excel、日期、PDF打印都有现成库不需要为一个小系统引入复杂的中间件。而且市面上 Python 开发者多小成本维护不容易被技术栈套牢。做这种小规模管理系统每次接口改完能快速重启迭代这比高性能重要得多。前端用 Vue3组合式 API 在写这种表单密集型系统时特别顺手组件之间共享状态用 Pinia表格、表单、日期选择器这些后台常用组件用 Element Plus 基本是拿来即用。相比 Vue2 的选项式写法Vue3 的 setup 语法糖在维护大量业务组件时逻辑复用和代码组织明显更舒服。2.2 后端接口层选型FastAPI 为什么比 Flask 更适合这里多说一句后端框架。如果你从 Flask 时代过来的可能会惯性选 Flask。但我这次用的是 FastAPI因为它在三个点上对这类项目更友好FastAPI 自带 OpenAPI 接口文档前后端联调时前端同学直接打开 /docs 页面就能看到所有接口和参数不需要我额外写接口说明文档。它用 Pydantic 做请求和响应校验POST 传参时手机号格式、金额范围这些东西写在校验模型里接口内部就不用一堆 if 判断。异步支持是原生语法虽然管理系统的并发压力不大但后面接一些第三方通知服务时协程写起来比 Flask 的线程模型清爽。2.3 项目目录和基础工程搭建我建项目时没有用复杂的分层架构因为这类系统规模不大过度设计反而是负担。目录结构大致是这样app/ main.py # FastAPI 入口注册路由、CORS、中间件 database.py # 数据库连接会话管理 models/ # SQLAlchemy 数据模型 schemas/ # Pydantic 校验模型 routers/ # 各业务模块路由 customers.py orders.py parts.py payments.py stats.py services/ # 业务逻辑层状态流转、库存扣减等 utils/ frontend/ src/ api/ # axios 接口封装 views/ # 页面组件 components/ # 通用业务组件 stores/ # Pinia 状态 router/后端环境准备这一步我整理了可以直接跑的初始化命令Windows和Linux都适用# 创建虚拟环境并激活 python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate # 安装依赖 pip install fastapi uvicorn sqlalchemy psycopg2-binary # 如果不想引入 PostgreSQL开发阶段可以直接用 SQLite后面再切 # 启动开发服务 uvicorn app.main:app --reload --port 8000前端用 Vite 脚手架创建 Vue3 项目装 Element Plus 和 Pinia然后跑起来npm create vitelatest frontend -- --template vue cd frontend npm install element-plus element-plus/icons-vue pinia vue-router axios npm run dev这套骨架搭完之后前后端就是两个独立的工程通过 HTTP 接口通信。开发期用 Vite 的代理把 /api 转发到 8000 端口生产环境再由 Nginx 统一转发。3. 后端核心设计维修订单、客户与配件的建模3.1 数据模型如何贴近维修店的真实业务流程数据建模是整个系统里最需要动脑子的地方因为它直接决定后续的业务逻辑能不能顺畅落地。我按维修店的真实流程把核心表拆成这样表名关键字段说明customersid, name, phone, address客户档案手机号唯一ordersid, order_no, customer_id, device_type, brand, model, fault_desc, status, assignee_id, fee_amount, warranty_end_at维修单主表一个客户一次可能送多件但一般拆成多张单order_itemsid, order_id, part_id, part_name, quantity, unit_price维修明细记录用了哪些配件及数量partsid, name, model, stock_quantity, purchase_price, sale_price配件库存表techniciansid, name, phone, commission_rate维修师傅用于派单和业绩统计paymentsid, order_id, amount, pay_method, paid_at收款记录一个维修单可能分次收款有个细节需要特别注意维修单和维修明细必须分开成两张表。一台洗衣机可能同时换了电机电容和水泵每个配件的价格、数量都要在明细里记清楚。如果图省事把所有配件塞进一个文本字段后面做成本核算的时候就哭都哭不出来。3.2 订单状态机设计一个维修单从进门到出门的完整流转维修单是这个系统的心脏它的状态变化必须非常明确。我设计的状态流转是这样的待接单received客户送修或登记单子创建还没指派给师傅。检修中repairing师傅接到单子开始检查并维修。待取件pending_pickup维修完成等客户来取件并结账。已完成completed客户取走机器款项结清。已取消cancelled客户不修了或店方无法修复退机。为什么要单独有一个待接单状态因为门店场景里单子创建后往往不会立刻分配师傅可能师傅还在忙或者这个新单子要等某个配件到货才能开工。没有这个中间状态单子要么只能写尚在处理要么就得跳过流程直接分配信息上会有断层。状态流转不是每个状态之间都能随便跳的。比如待接单不能直接变已完成已完成理论上也不应该再退回检修中。我在 service 层写了一个状态变更方法只允许特定的合法转换非法操作直接抛业务异常。3.3 关键接口与业务逻辑实现示例接口设计遵循一个简单的原则客户端只负责传参和展示业务规则全部放在服务端。下面这个是创建维修单的接口注意 Pydantic 校验模型怎么把抽屉里的信息约束住from pydantic import BaseModel, Field class OrderCreate(BaseModel): customer_id: int device_type: str brand: str model: str fault_desc: str Field(..., min_length1, max_length500) assignee_id: int | None None items: list[OrderItemCreate] []订单状态变更的核心逻辑我写在 service 层这样路由层就变得非常薄from app.models import Order from app.services.exceptions import BusinessError ALLOWED_TRANSITIONS { received: {repairing, cancelled}, repairing: {pending_pickup}, pending_pickup: {completed}, } def change_order_status(db, order: Order, new_status: str, user_id: int): if new_status not in ALLOWED_TRANSITIONS.get(order.status, set()): raise BusinessError(f非法状态流转{order.status} - {new_status}) order.status new_status ...一个非常容易踩的坑配件扣减必须放在检修中或待取件这个确认节点而不是放在订单创建时。维修单创建的时候师傅可能还在思考要不要换配件此时扣库存会把账面搞得很混乱。我最后把扣减动作放在状态变成待取件的那一步也就是维修已经完成、配件确认用了这时才真正扣减库存并生成一份配件出库记录。4. 前端 Vue3 界面把业务流程搬到浏览器里4.1 页面结构与路由设计前端这块我一开始就拒绝了买现成模板的想法因为维修店业务比较固定页面自己拼反而更干净。路由设计对应于后端模块const routes [ { path: /login, component: Login }, { path: /dashboard, component: Dashboard }, { path: /customers, component: CustomerList }, { path: /orders, component: OrderList }, { path: /orders/:id, component: OrderDetail }, { path: /parts, component: PartList }, { path: /payments, component: PaymentList }, { path: /reports, component: ReportList }, ]其中订单详情页是工作量最大的页面因为它既要有基础信息展示又要有状态操作按钮接单、完成、取消还得能增删维修明细。我建议把所有操作都集中在这个页面里列表页只负责筛选和入口。4.2 Pinia 管理全局状态Axios 对接后端接口登录状态、当前用户的权限、订单列表的过滤条件这些属于跨页面的全局状态用 Pinia 管理最方便。我在 userStore 里保存 token 和用户信息在 orderStore 里保存列表查询条件这样从列表页点进详情页再返回筛选条件不会丢体验会好很多。Axios 封装也是必须的。我最开始在组件里直接 this.$http.get后来发现每次都要处理 token 和错误提示代码极其重复。封装之后每个接口调用只要一行// src/api/request.js import axios from axios const request axios.create({ baseURL: /api, timeout: 10000, }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) config.headers.Authorization Bearer ${token} return config }) request.interceptors.response.use( response response.data, error { // 401 跳登录其他错误统一 ElMessage 提示 return Promise.reject(error) } )这样一个 token 注入和错误提示就全统一了页面里不需要再重复 catch 错误。还有一个细节Element Plus 的表格组件在数据量大时要搭配 server 端分页不要一次把全表数据拉到前端我一开始图省事全量加载结果配件表两千条记录就让页面明显卡顿后来改成后端分页接口瞬间流畅了。4.3 组件化拆分的经验哪些页面能共用组件我一开始犯的错误是每个页面都写自己的模板后来发现很多结构高度重复。我把这些抽成了公共组件StatusTag.vue根据订单状态显示不同颜色的标签。DeviceTypeSelect.vue家电品类下拉选择器数据可以来自后端字典表。CustomerSearch.vue按手机号/姓名搜索客户的组件维修单和收款页面都要用。ItemsEditor.vue可以动态增删配件的明细编辑组件这是整个系统操作最频繁的部分之一。尤其是 StatusTag别看它小收益非常大。状态如果只在后端是字符串前端一不小心就写错一两个常量导致标签渲染不出来。我用一个字典配置把所有状态对应的文案、颜色集中管理同一份配置在列表页、详情页、统计页复用改一处全局生效。5. 家电维修场景特有的业务细节保修、配件与旧件管理5.1 保修期计算字段存储到期时间比动态计算更可控家电维修店最典型的业务规则就是保修。不同维修项目保修期不一样一般更换的配件保修三个月整机维修保修一个月。这个需求如果等系统上线以后再临时加就要改动表结构所以要在一开始设计订单的时候就预留保修到期时间字段。这里我想着重说一个取舍保修到期时间到底是存一个日期还是只存一个保修天数然后每次动态计算我最终选择存到期日期也就是warranty_end_at。原因很简单查询今天有哪些订单快要过保的时候直接一条 SQL 就能筛出warranty_end_at在七天内的订单。如果存天数每次都要在created_at days上传运算索引就废了。后续如果对某些订单人工延期保修只需改到期日期这一个字段不用额外记录延期原因。保修期提醒我做了个简单方案工作台首页放一个即将过保列表把七天内到期的订单列出来方便客服给老客户打电话做回访。别小看这个功能它能让客户和门店之间保持联系很多复购和推荐都是这么来的。5.2 配件库存联动修完一台机器库存自动对得上账配件库存是维修店最容易出乱子的地方因为师傅换配件和前台录库存往往不是同一个人动作还发生在不同的时间点。我在系统里把配件出库绑定到订单状态流转的阶段规则是订单进入待取件时遍历该订单的维修明细把每项配件数量从库存中扣减同时写入一条配件流水记录。流水是为追溯准备的某天发现某种配件莫名其妙少了查流水就知道是哪个订单、哪个师傅、哪台机器消耗的。配件入库则单独走采购入库逻辑入库时先增加库存同时记录采购批次和单价。月底盘库时如果账面数量和实际数量不一致就顺着流水追责而不是像以前那样一家店三个账本各说各的。5.3 旧件的去留状态记录换下来的旧件怎么处理这个细节很多管理系统都不做但它对维修店来说其实挺重要。换下来的旧压缩机有的客户要拿走有的说不要了留在店里还有的之后会被回收给配件商。不管是哪种情况如果不记录过几天再问谁都不记得。所以我在维修明细表里加了一个字段old_part_status有三种取值returned退给客户、discarded丢弃、kept店内留存。客户取件的时候客服看着系统确认旧件的去向然后把这个状态点掉。别看这个字段小它避免了大量售后纠纷——客户说我上次换下来的主板你们把我的拿走了系统一调记录谁说过什么一清二楚。6. 部署上线与真实踩坑记录6.1 前后端分离部署的基本布局这个系统最后是部署在朋友门店一台旧电脑上的配置不高但也够用了。部署结构很简单后端用uvicorn app.main:app --host 0.0.0.0 --port 8000启动生产环境套一层 gunicorn 或者 uvicorn 的多 worker 模式。前端npm run build之后把 dist 目录扔给 NginxNginx 同时配置接口转发把/api代理到后端端口。server { listen 80; server_name your-domain.com; root /home/user/frontend/dist; index index.html; location /api { proxy_pass http://127.0.0.1:8000; } }6.2 五个具体坑挨个说给大家听CORS 跨域问题开发期前端在 5173 端口后端在 8000 端口直接请求会被浏览器拦截。FastAPI 需要配置 CORSMiddleware允许的来源要写清楚。我一度把allow_origins写成了[*]结果带 cookie 的请求还是失败因为跨域携带凭证和通配符不能共存最后老老实实写了前端的具体地址。时区问题数据库存时间如果默认存的是 UTC而店里打单要的是北京时间展示时差八个小时客户取件单上的时间全对不上。我的处理方案是统一使用datetime.utcnow存 UTC展示层转北京时间或者干脆直接用Asia/Shanghai时区创建时间。对业务来说展示时间必须准确这个细节在联调时最容易忽略。图片上传后无法访问维修前给故障家电拍照是个很实用的功能但照片上传到后端某个目录后前端访问不到原因是 Nginx 没有把静态目录暴露出来。我在 Nginx 里单独配了一个/files的 location 指向上传目录问题解决。要记住后端返回图片 URL 时应该返回一个可直接访问的完整路径而不是本地磁盘路径。备份策略没提前做开发的时候不觉得真正上线我第一周就碰到了断电导致 SQLite 文件损坏的潜在风险。后来我改用 PostgreSQL并写了个简单的定时备份脚本每天凌晨用pg_dump导出数据库文件同时同步备份上传的图片目录。对门店系统来说数据量不大备份策略也很简单但必须有。CSV 导出乱码导出配件库存或者维修记录时如果用 CSV 格式Windows 上的 Excel 打开会乱码。这个问题很多人反复踩解决办法是写 CSV 时加上 BOMutf-8-sig编码一份改动解决所有 Windows 电脑的乱码问题。6.3 给小店部署的小建议如果你是想给自家小店或者朋友小店部署这套系统我的建议是别追求什么微服务、容器编排一台普通电脑、一个数据库、日常备份就足够了。真没必要把简单事情搞复杂。业务跑通以后如果遇到响应变慢看看是不是没加索引先优化慢查询再考虑升级硬件。另外要给账号分下权限老板能看所有页面和统计前台能录单和结算师傅只能看分配给自己的维修单。这个权限模型在一开始就加上不然后期再改会非常痛苦。做完整套系统再回头看我最深的体会是工具和技术是次要的真正重要的是把维修店每天的信息流梳理清楚。客户来电、师傅检修、配件出库、收款结账这些动作如果有一条清晰的数据链串起来店里的运营效率提升是立竿见影的。而 Python 加 Vue3 这个组合恰好让我在短时间内把这条链路从纸质搬到了屏幕上这也是我拿到这个需求时最终选型的底气所在。
返回列表