资讯详情

Python后端+Vue前端:设备采购管理系统从零搭建全指南

📅 2026/10/18 6:14:49 | 华诺云谱 👁 阅读
Python后端+Vue前端:设备采购管理系统从零搭建全指南
设备采购管理系统从零搭建详解 Python 后端 Vue 前端这套经典组合做管理系统也有六七年了陆陆续续帮人改过设备管理、固定资产、办公用品申领之类的系统。最近又有一位同学拿着“基于 Vue 的 设备采购管理系统 django flask pycharm”这个选题来找我说是要交毕设让我帮忙看看技术路线怎么搭。这让我觉得有必要把这类项目从需求拆解到落地实现的完整链路写出来因为无论哪个行业、什么深度这类系统出现的频率实在太高了而且踩的坑来来去去就那么几个。这套系统核心用 Python 做事后端框架在 Django 和 Flask 之间二选一前端交给 Vue开发环境用 PyCharm。它能解决的问题很直接把线下那套“申请-审批-下单-入库”的纸质流程搬到线上让设备采购过程的每一步都有记录、可追踪、能统计。适合正在做课程设计或毕业设计的同学也适合刚入门全栈开发、第一次接触前后端分离项目的从业者参考。文章里会按真实开发顺序来写从技术选型讲到最后联调代码和配置尽量给到可复用的程度。1. 技术选型为什么是 Django/Flask Vue PyCharm很多人在动手前最纠结的就是选型问题。标题里写的是“django flask pycharm”你没看错这是两个框架并列出现。在实际开发中Django 和 Flask 确实常常被放在一起比较但真正做项目时通常只会二选一。到底怎么选取决于你对项目规模和开发效率的判断。1.1 后端框架Django 还是 Flask先说结论设备采购管理系统这种业务型应用我的建议是优先选择 Django。原因不是 Flask 不好而是 Django 恰好把这类管理系统的通用需求都提前解决了。Django 自带一个完整且合理的 ORM数据库迁移、外键关联、日期字段处理都是开箱即用。管理系统里大量操作都是围绕增删改查展开的Django 的 ORM 能让我少写至少三分之一的重复代码。其次是认证授权Django 自带的 auth 用户系统和新版权限分组机制几乎就是为后台管理系统设计的。设备采购系统天然需要区分普通员工、部门主管、采购专员、系统管理员这些角色Django 内置的 Group 和 Permission 稍微改造一下就能用。此外 Django 自带的后台管理界面可以让我在开发阶段零成本维护一份基础数据。Flask 的强项是轻量、灵活适合对整体架构有强掌控欲或者项目规模很小的场景。它不会替你做默认决策你需要自己安装 Flask-SQLAlchemy、Flask-Login 等扩展来拼凑功能。如果只是做十几个接口的小项目这没问题但当你需要开发审批流、权限过滤、报表聚合这类功能时自己组装效率确实低很多。对比项DjangoFlask内置 ORM完整迁移机制可靠需要外接 SQLAlchemy用户认证自带 auth 系统需要自己集成后台管理自带 Admin 后台无需要自己写项目结构约定大于配置清晰统一自由灵活依赖个人习惯学习成本较高但一次投入长期受益较低但后续补充知识较多适合场景管理系统、内容平台、中大型业务系统轻量 API、微服务、快速原型1.2 前端为什么选 VueVue 在前端三大框架里属于学习曲线最友好的一个。设备采购管理系统需要处理的界面大多是表单、表格、弹窗、树形菜单这类常见交互Vue 的组件化开发方式加上 Element Plus 组件库几乎能覆盖 90% 的页面需求而且视觉上足够专业。选择 Vue 还有一个现实原因是生态成熟。Vue Router 做页面跳转、Vuex 或 Pinia 做状态管理、Axios 做 HTTP 请求这套组合已经是市场最主流的前后分离技术栈。刚入门的人遇到问题时中文资料和示例项目都很好找不容易被卡住。前后端分离的开发方式也让任务分工变得清晰前端专注页面的数据展示与交互后端专注业务逻辑和接口输出。调试阶段两边互不干扰用 PyCharm 的断点调试功能检查后端逻辑的同时前端也能在浏览器 DevTools 里查看网络请求和响应内容。1.3 PyCharm 在开发中的实际角色标题里特意提到 PyCharm说明绝大多数人在这个项目里用的开发工具就是它。PyCharm 专业版对这类项目的支持相当到位Python 后端代码有完整的补全和调试体验内置的 HTTP Client 可以用来直接测试接口专业版还额外支持 Vue 语法高亮和前端模板渲染。不过这里有个小提醒PyCharm 社区版是免费的但不带 Vue 插件。如果不想付费最常的应对方案是安装 VSCode 写前端、PyCharm 写后端也有很多人直接用 PyCharm 专业版搞定全栈体验更加连贯而学生用户在教育优惠下通常可以免费拿到专业版授权。就我个人习惯来说用 PyCharm 配置虚拟环境、管理数据库连接、打断点调试 Django 接口的效率确实是最稳的一套组合。当你需要同时在浏览器、数据库、后端接口三端来回切换时一个工具把这些都串在一起的价值就体现出来了。2. 需求拆解设备采购管理系统的模块与数据模型技术确定后下一步是搞清楚系统到底要管什么、有哪些角色参与、每个角色需要看到什么页面。这一步如果做糊了后面写代码必然反复返工。我习惯在写任何一行代码之前先用手头的数据把整个流程走一遍。2.1 角色权限怎么划分设备采购系统一般会涉及这几类角色考虑到实际使用的灵活度我不会把角色专门建表而是用 Django 自带权限系统配合 User 表的 user_type 字段来实现角色核心权限说明普通员工提交采购申请、查看自己的申请记录只需要最小可见范围部门主管审批下属的采购申请能看到自己部门的所有申请采购专员处理已审批的申请单参与询价、比价、生成采购订单核心业务操作者资产管理员设备到货后做验收和入库登记是采购流程的收尾环节系统管理员维护设备分类、供应商信息、分配用户角色和权限整个系统的管理入口这套角色设计基本覆盖了企业内部通用的大中型设备采购流程。如果只是做课程项目可以适当合并比如把采购专员和资产管理员合并成一个角色整体流程仍然完整。2.2 核心业务模块要拆多细设备采购管理系统在功能上可以拆成几个大模块每个模块实际对应几张数据库表和若干接口设备档案模块维护设备分类、设备名称、规格型号、参考单价这是采购申请时下拉菜单的数据来源。采购申请模块填申请单、选设备、填数量、填预算来源这是整个系统最关键的表单。审批流程模块主管查看待办、点击同意或驳回系统记录每一步审批的动作和时间。供应商管理模块维护供应商名称、联系人、报价历史采购比价的时候需要用到。采购订单模块把审批通过的申请单转成采购订单记录合同编号、下单时间、交货期限。入库登记模块到货后登记入库设备数量、存放位置、验收结果同时回写订单状态。统计报表模块按时间维度统计采购数量、采购金额、审批耗时这部分主要是数据分析的需求。从工程的角度看这些模块本质上都是对若干数据表的操作。后台管理系统的难点从来不是单张表怎么处理而是表与表之间的业务流转怎么串联。2.3 数据库表结构设计思路以“申请单”为中心我会设计这样几长表Device设备表name、model_no、category_id外键、unit_price、statusCategory分类表name、parent_id自关联实现树形层级PurchaseApplication采购申请单applicant_id外键用户、device_id外键设备、quantity、expected_price、purpose、status、created_atApprovalRecord审批记录表application_id外键、approver_id、action同意/驳回、comment、created_atSupplier供应商表name、contact_person、phonePurchaseOrder采购订单表application_id外键、supplier_id、total_amount、order_status、delivery_dateStockInRecord入库记录表order_id外键、device_id、quantity、location、handover_user有两处设计需要特别留意。第一处是审批记录表。采购申请的状态变化不应该只用一个 status 字段去更新还要把“谁在什么时间做了什么操作”完整记录下来。这样后续管理审计、复盘审批耗时才有数据支撑。审批状态流转本质上是有限状态机从待审批移到已通过或已驳回每一步都要写入记录表。第二处是外键约束的选择。比如设备表如果被采购申请单引用删除设备时到底该报错还是连带删除如果设备已存在历史申请记录物理删除会破坏数据关联的完整性。这里的经验做法是把设备表做成逻辑删除加一个 is_active 字段控制显示状态而不是真正从库里删掉。百试不爽。3. 实操从零搭起前后端分离的采购管理项目技术选型和表结构定下来接下来就可以真正开工了。这一节我会把项目初始化、后端接口、前端页面、联调配置的完整过程写出来手头的项目是真实可以跑起来的。3.1 后端项目初始化与模型实现先创建虚拟环境和 Django 项目mkdir procurement_system cd procurement_system python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install django djangorestframework django-cors-headers django-admin startproject config . python manage.py startapp device python manage.py startapp purchase python manage.py startapp supplier项目结构里config 是配置目录device、purchase、supplier 是业务应用。使用前我建议在 config/settings.py 中把本地应用和第三方应用注册进 INSTALLED_APPS包括 rest_framework、corsheaders以及新创建的三个应用。模型代码以设备表和申请单为例# device/models.py from django.db import models class Category(models.Model): name models.CharField(max_length64, verbose_name分类名称) parent models.ForeignKey(self, nullTrue, blankTrue, on_deletemodels.CASCADE, verbose_name父级分类) class Device(models.Model): name models.CharField(max_length128, verbose_name设备名称) model_no models.CharField(max_length64, verbose_name规格型号) category models.ForeignKey(Category, on_deletemodels.PROTECT, verbose_name所属分类) unit_price models.DecimalField(max_digits10, decimal_places2, verbose_name参考单价) is_active models.BooleanField(defaultTrue, verbose_name是否启用)# purchase/models.py from django.db import models from django.contrib.auth.models import User class PurchaseApplication(models.Model): STATUS_CHOICES [ (pending, 待审批), (approved, 已通过), (rejected, 已驳回), ] applicant models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name申请人) device models.ForeignKey(device.Device, on_deletemodels.PROTECT, verbose_name采购设备) quantity models.PositiveIntegerField(verbose_name采购数量) expected_price models.DecimalField(max_digits10, decimal_places2, verbose_name预算单价) purpose models.TextField(verbose_name采购用途) status models.CharField(max_length10, choicesSTATUS_CHOICES, defaultpending, verbose_name审批状态) created_at models.DateTimeField(auto_now_addTrue, verbose_name申请时间)有几处细节值得展开说。Device 外键指向分类用 on_deletemodels.PROTECT意思是有设备还在该分类下时不允许删除分类这个特性可以避免误操作导致分类丢失。PurchaseApplication 里 applicant 指向 Django 自带 User 表没有自己单独建用户表这对中小型系统来说更省事。主流的设备采购系统基本都是这个套路用户信息继续挂在 auth 表上业务表通过外键引用。模型写完后依次执行python manage.py makemigrations python manage.py migrate3.2 核心接口实现审批流与查询列表Django 搭配 DRF 写接口的效率比纯原生 JsonResponse 高很多。以申请列表接口为例开发阶段会同时考虑分页和搜索保证前端操作流畅# purchase/views.py from rest_framework import generics from rest_framework.filters import SearchFilter from rest_framework.pagination import PageNumberPagination from .models import PurchaseApplication from .serializers import PurchaseApplicationSerializer class ApplicationPagination(PageNumberPagination): page_size 10 page_size_query_param page_size class ApplicationListView(generics.ListCreateAPIView): serializer_class PurchaseApplicationSerializer pagination_class ApplicationPagination filter_backends [SearchFilter] search_fields [device__name, applicant__username, purpose] def get_queryset(self): user self.request.user # 部门主管看本部门普通员工只看自己的 if user.userprofile.role manager: return PurchaseApplication.objects.filter(applicant__departmentuser.department) return PurchaseApplication.objects.filter(applicantuser)审批操作的实现重点在后端校验角色权限与状态前置条件这部分必须写得严谨def perform_approve(self, application_id, approve): app PurchaseApplication.objects.select_for_update().get(idapplication_id) if app.status ! pending: raise ValidationError(该申请单已审批过) app.status approved if approve else rejected app.save(update_fields[status]) ApprovalRecord.objects.create( applicationapp, approverself.request.user, actionapproved if approve else rejected, )select_for_update 是 Django 提供的一个数据库级锁操作多个用户同时审批同一条请求时它会保证只有第一个请求能完成状态变更后来的请求会看到最新状态并提示“已审批过”。3.3 Vue 前端页面搭建与路由控制前端我通常用 Vite 来搭建 Vue 项目相比 Vue CLI 它启动更快打包也更利落。创建命令如下npm create vuelatest frontend cd frontend npm install npm install axios element-plus vue-router pinia进入 src 目录后把请求封装和路由配置作为最先落地的两个基础能力。请求封装要处理 token 附带与响应拦截器这一步几乎影响后续所有接口的调用方式// src/utils/request.js import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: http://localhost:8000/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 { ElMessage.error(error.response?.data?.detail || 请求失败) return Promise.reject(error) } )路由配置里加一个全局前置守卫未登录的访问统一跳到登录页// src/router/index.js import { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(), routes: [ { path: /login, component: () import(/views/LoginView.vue) }, { path: /dashboard, component: () import(/layout/MainLayout.vue), children: [ { path: , component: () import(/views/ApplicationListView.vue) }, { path: approval, component: () import(/views/ApprovalView.vue) } ] } ] }) router.beforeEach((to) { if (to.path ! /login !localStorage.getItem(token)) { return /login } })申请列表页的核心是拿到接口数据后渲染表格加上筛选和分页组件这是一套固定打法。给一个关键片段// src/views/ApplicationListView.vue const pageData ref({ records: [], total: 0 }) const queryParams reactive({ page: 1, page_size: 10, search: }) async function fetchData() { const data await request.get(/applications/, { params: queryParams }) pageData.value data } function handleStatus(row) { const map { pending: 待审批, approved: 已通过, rejected: 已驳回 } return map[row.status] }这是一整个体系里的细节前端页面说白了就是列表、弹窗、表单、详情四件事熟练之后搭一个模块的页面基本就是复制改改。3.4 前后端联调关键配置与登录认证前后端分离后最常见的问题就是跨域。Django 后端需要允许前端的开发服务器访问# config/settings.py INSTALLED_APPS [ # ... corsheaders, rest_framework, rest_framework_simplejwt, ] MIDDLEWARE [ # ... corsheaders.middleware.CorsMiddleware, # ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, ] REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework_simplejwt.authentication.JWTAuthentication, ], }登录认证这块思路也比较固定。后端用 JWT 方式发 token前端登录成功后把 token 存到 localStorage每次请求在拦截器里带上 Authorization 头未登录用户由路由守卫统一拦回登录页。这套流程在设备采购管理系统里已经足够安全也不需要额外的 session 管理机制。在 PyCharm 里调试前后端配合的问题时有一个做法很有效先在后端接口处打上断点再到前端页面发起请求通过观察断点处的调用栈和数据内容可以快速判断问题到底出在请求参数、权限校验、还是数据处理逻辑上。相比在两个端各自瞎猜这种“用后端断点反向验证前端请求”的方式效率高很多。你也可以打开浏览器的 Network 面板先确认请求是否到达后端再看响应内容是否符合预期。4. 开发中常见的坑与排查记录这类项目从一跑到全流程跑通大概率会碰见下面这些让人头疼的问题。我挑出现频率高的几个结合自己的排查过程整理成清单供大家参考。4.1 跨域请求失败控制台提示被 CORS 拦截这个问题在前后端分离项目中几乎必现。如果你忘了安装 django-cors-headers或者 CORS_ALLOWED_ORIGINS 里没有写对前端的地址特别是 5173、3000 这类端口浏览器就会直接拦截后端响应。前端可以看到请求发出去但控制台报错后端日志里也不见任何访问纪录。排查思路先确认安装和中间件顺序正确再检查白名单地址与前端访问地址完全一致。需要注意 localhost 和 127.0.0.1 在某些浏览器策略里会被视为不同地址尽量统一使用一个。4.2 时间字段显示不对少 8 小时Django 默认按 UTC 存储时间如果你的系统和服务不在同一个时区前端展示时就会出现“创建时间是下午 3 点但页面上显示早上 7 点”这种怪象。处理方案是在 settings.py 里设置TIME_ZONE Asia/Shanghai USE_TZ FalseUSE_TZ 设为 False 会让 Django 直接使用本地时间存取对单机部署的内部管理系统来说更直观。如果坚持用 USE_TZTrue前端需要做 UTC 转本地的处理相对麻烦一些。4.3 删除设备时提示外键限制删不掉这个现象反而说明你的 on_delete 策略非常正确。设备表被采购申请单引用时如果用的是 PROTECT有历史申请记录的设备根本删不掉。这时候不要试着把外键改成 CASCADE 去强删正确的做法是给设备表加 is_active 布尔字段做逻辑下架而不是物理删除。历史数据保持完整新申请单也选不到已下架的设备两全其美。4.4 分页后筛选条件失效列表接口很常见的坑是第一页有筛选结果翻到第二页筛选项就被重置了。原因多数在于前端筛选表单的搜索按钮没有把搜索词和页码同步提交给后端或者是分页组件切换页码时没有带上已有查询参数。我在代码里统一把 page、page_size、search 放进同一个 queryParams 对象任何条件变化都触发一次完整的 fetchData而不是独立更新某一个参数。4.5 已登录用户刷新页面后仍跳登录页这种场景通常是 token 存在 localStorage 里但刷新时 Pinia 或组件里的用户信息变成空对象路由守卫误判为未登录。解决的常规做法是在路由守卫里拦截到 token 后再调用一次获取当前用户信息的接口拉取完再放行保证用户信息完整恢复。下面把排查方法集中整理成速查表问题现象可能原因排查与解决请求被 CORS 拦截后端未开 CORS 或白名单写错安装 django-cors-headers核对地址和端口时间字段少 8 小时Django 默认 UTC前端未转换设置 USE_TZFalse 或在序列化器里做时区格式化设备删不掉报外键错误使用 PROTECT 关联加 is_active 字段做软删除不物理删除分页后筛选失效查询条件没有和页码一起传将筛选项和页码统一放入 queryParams 一并提交刷新页面就退出登录token 有但用户信息未恢复路由守卫中重新请求用户信息再放行后端接口可以访问但前端请求时报 403认证头没有带上 token检查 request 拦截器应该附带 Bearer Token4.6 审批流并发处理select_for_update 详解这里再单独展开一下审批环节的并发问题因为这是业务型管理系统的关键细节。设想一个场景两个审批人同时打开同一张待审批的申请单一个点了通过另一个点了驳回如果后端的处理逻辑只是先读状态再写状态最后的结果必然以最后一次写入为准相当于前一个人的操作被静默覆盖掉了这在业务上是不可接受的。使用 select_for_update 后Django 会在数据库层面锁定这一行记录直到事务提交为止。第二个请求在等待锁的期间结束再读取时会发现该申请的状态已经不再等于 pending于是直接抛出“该申请单已审批过”的校验异常前端也就不会继续执行后面那步写操作。这个机制在实际生产中非常必要但很多入门教程里不会提到。建议在课程设计中把这个点写成亮点老师提问“并发场景怎么处理”的时候这个回答是很有说服力的。5. 项目扩展与个人经验做完这套采购系统后最常被问到的就是“还能往哪些方向扩展”。从我实际改过几个版本的经验来看有几个方向确实值得做增加消息通知中心审批结果通过站内信或者邮件自动通知申请人增加 PDF 版的采购申请单导出方便线下归档签字引入更细粒度的权限比如采购预算按部门单独控制统计报表做成可视化图表用 ECharts 展示每月采购金额趋势。这些扩展功能大多能基于现有的前端组件和后端接口快速实现也是给项目加分的性价比很高的方向。最后再分享一点个人体会。写这种管理系统业务逻辑的完整度比技术的炫酷程度更重要。先花时间把角色的操作路径、状态的流转规则梳理清楚再动手写代码整个开发过程会顺利很多。另外就是项目里务必保留几个必要的单元测试像审批状态不能重复流转、普通员工只能看到自己提交的申请这类用例每次改动后跑一下能帮你省下大量手工验证的时间。千万别觉得测试是多余环节在后期演示系统中稳定运行的效果才是真正让评审或面试官眼前一亮的东西。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑