ARTICLE DETAIL

资讯详情

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

滤清器材料仓库管理系统实战:基于Django+Vue的业务建模与库存设计

滤清器材料仓库管理系统实战:基于Django+Vue的业务建模与库存设计 做滤清器行业的仓库管理系统最容易被低估的其实是“业务建模”这一步。过滤器、滤清器材料看着就是些滤纸、滤芯、密封圈、端盖但真到了要管批次、管规格、管安全库存的时候你会发现Excel表格根本撑不住。这个项目标题很有意思——flask过滤器滤清器材料仓库管理系统但技术栈里又同时提到Django和Vue我猜不少人第一次看到会愣一下到底是Flask还是Django这正是我想在这篇博文里展开聊的。我会从需求拆解、技术选型、后端实现、前端对接、部署排雷五个部分把整个系统的设计思路和关键代码过一遍。无论你是刚学完Python基础想找完整项目练手还是已经在做进销存类管理系统想参考一下库存逻辑这篇内容都能直接抄作业。1. 需求拆解滤清器材料的仓库管理到底难在哪很多新手拿到项目第一反应就是建表、写接口、画页面跳过了最要命的需求分析。滤清器材料仓库管理系统表面上是“库存增删改查”但实际上有几个行业特有的难点不拆清楚后面代码写再多都是空壳。1.1 从“囤货”到“周转”滤清器材料的管理对象先看这个仓库里到底存了什么。过滤器滤清器材料按用途可以粗分为五大类过滤介质滤纸、滤芯、滤网、密封组件密封圈、垫片、结构件端盖、骨架、中心管、粘接材料胶黏剂、热熔胶、辅助耗材护网、弹簧。每一类材料在入库、存储、领用时的管理粒度完全不一样。再看规格维度这才是真正要命的地方。同一种滤芯可能因为过滤介质不同空气、机油、燃油、液压油、过滤精度不同5μm、10μm、200目、外形尺寸不同外径、内径、高度、耐温等级不同被拆成几十个SKU。如果只是把“名称”当唯一标识库存账不到一个月就会乱成一锅粥。所以我在设计数据模型时给材料主数据设计了一个“编码体系 规格参数”的双层结构。材料编码是全局唯一的业务主键格式类似FX-001-AIR-5UM-80X120其中FX代表滤芯001是流水号AIR是介质类型5UM是过滤精度80X120是尺寸。规格参数则用独立的字段存方便前端做筛选和报表统计。1.2 角色权限与出入库流程管理系统的“骨架”仓库系统的角色不需要搞得很复杂但至少要覆盖四种人仓库管理员负责日常入库、出库、盘点采购人员关注安全库存预警库存低于阈值时触发采购建议生产领料员只关心“我要的料还有没有、在哪个库位”系统管理员负责基础资料和用户权限。在这个项目里我把出入库流程设计为“单据 流水”双轨制。仓库管理员先创建入库单或出库单审核通过后系统自动生成库存流水库存数量实时变动。这样设计的好处是账实分离——单据是业务凭据流水是数据轨迹出问题能追溯对账也不靠猜。用户权限这块Django自带的auth模块加上Django REST Framework的权限类就能满足90%的需求不需要一开始就上RBAC重框架。我用的是IsAuthenticated 自定义HasGroupPermission权限类按用户组区分角色简单直接。2. 技术选型为什么最终选了 PyCharm Vue Django项目标题里同时出现Flask和Django这其实是很多Python Web开发者的共同困惑。我在实际开发中两套框架都用过简单说Flask胜在轻量灵活适合做API原型和中小型服务Django胜在全家桶齐全自带ORM、Admin后台、认证体系和数据库迁移工具做管理系统这类“业务逻辑重、表格多、权限繁”的项目开发效率高一个量级。2.1 Flask、Django之争的实操理解标题里的“flask”很可能是三种情况一是写作时的笔误把Django写成了Flask二是原型阶段确实用Flask快速做过Demo后面才切到Django重构三是打算做前后端分离Flask只负责APIVue负责页面。无论哪种情况落到“仓库管理系统”这个具体场景我都推荐以Django为主后端。原因很实在仓库管理系统信息密度高需要大量联表查询和复杂筛选Django ORM的QuerySet链式调用写起来比Flask裸写SQL或SQLAlchemy省心太多系统需要一个给仓管员用的后台管理界面Django Admin虽然是面向开发者的通用后台但配合自定义ModelAdmin配置几行代码就能出一个可用的数据管理界面这在项目早期非常宝贵用户认证、权限分组、CSRF防护这类基础设施Django原生就有Flask都要自己拼。前端用Vue 3 Element Plus后面我会展开说。开发工具选PyCharm是因为它对Python和前端都有良好支持社区版免费也够用专业版多了数据库工具和前端调试能力能进一步提升效率。有人纠结这项目是不是非得用PyCharm——不是VSCode也能干但PyCharm的调试器对Django请求链路追踪太友好了断点打到视图函数里直接看Request对象新手排查问题能少走很多弯路。2.2 环境搭建从零开始跑起这套技术栈先列一下我使用的软件版本避免版本不兼容造成“配置半小时运行五分钟报错”的尴尬组件版本说明Python3.103.8以下不推荐Django新特性支持不好Django4.2 LTS长期支持版本稳定Django REST Framework3.14写API用djangorestframework-simplejwt5.xJWT认证django-cors-headers4.x解决跨域Vue3.4前端框架Vite5.x前端构建工具Element Plus2.x组件库MySQL8.0主数据库PyCharm2023.3IDE后端项目创建我习惯先用命令行django-admin startproject warehouse然后python manage.py startapp materials、python manage.py startapp stock、python manage.py startapp users按业务模块拆分app而不是把所有逻辑塞进一个app里。PyCharm里配置Python环境时选venv虚拟环境依赖全部用requirements.txt管理部署到服务器时一行pip install -r requirements.txt搞定。前端我用npm create vuelatest创建工程选上Vue Router和Pinia。然后装核心依赖npm install element-plus axios pinia vue-router。Element Plus按需引入更优雅但为了方便我在项目里是全局引入的开发阶段无所谓打包后体积大不了多少。Vite的server.proxy配置成把/api代理到http://127.0.0.1:8000这样本地开发前端就不用处理跨域代理配置在vite.config.js里。3. 后端核心模块设计与实现从数据模型到库存逻辑后端是整个仓库系统的大脑数据模型设计得好不好直接决定后面写代码是顺风顺水还是步步踩坑。这一节我会把关键模型和核心业务逻辑都贴出来你照着敲一遍就能理解整个系统的运转机制。3.1 Django的MTV模式在这个项目里是怎么落地的Django的MTV模式——Model、Template、View——很多初学者只是把它背下来应付面试没有真正理解它在项目分工中的作用。说白了MTV就是一套职责分离的约定Model管数据View管业务Template管展示。在前后端分离的项目里Template这一层被Vue接管了View从传统函数视图变成了API视图但核心思想没变。拿一个“查询低库存材料”的需求举例。浏览器请求/api/materials/low-stock/?threshold50Django路由把请求交给视图函数视图函数调用Model管理器查数据库返回JSON数据前端Vue拿到JSON渲染表格。整个过程里Model层负责“怎么查更高效”用索引、用select_related避免N1查询视图层负责“返回什么数据、要不要鉴权”Vue层负责“数据怎么展示、用户怎么交互”。每一层各司其职出问题能精准定位。我实际建项目时usersapp里放用户模型和登录注册接口materialsapp里放材料主数据模型stockapp里放库存、出入库单、流水这几个核心模型。按业务域划分app而不是按“model、view、serializer”这种技术层面划分后期维护会舒服得多。3.2 数据模型设计给材料建一张“可追溯”的底账不废话直接上关键模型代码。from django.db import models from django.core.validators import MinValueValidator class Material(models.Model): CATEGORY_CHOICES [ (FILTER, 滤芯), (MEDIA, 滤纸), (SEAL, 密封件), (STRUCT, 结构件), (BOND, 粘接材料), ] code models.CharField(材料编码, max_length64, uniqueTrue) name models.CharField(材料名称, max_length128) category models.CharField(分类, max_length16, choicesCATEGORY_CHOICES) spec models.CharField(规格参数, max_length255) unit models.CharField(计量单位, max_length16, default个) safety_stock models.DecimalField(安全库存, max_digits12, decimal_places3, default0) media_type models.CharField(过滤介质, max_length32, blankTrue) precision models.CharField(过滤精度, max_length32, blankTrue) shelf_life_days models.IntegerField(保质期天数, nullTrue, blankTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Meta: db_table material ordering [code] def __str__(self): return f{self.code}-{self.name}核心字段就这些。code唯一约束必须加这是防止重复数据的最后一道防线safety_stock用DecimalField而不是FloatField避免浮点误差media_type和precision是滤清器材料的特有维度普通仓库系统没这俩字段但在这里它们直接决定了材料的可替代性。class Stock(models.Model): material models.ForeignKey(Material, on_deletemodels.PROTECT, related_namestocks) batch_no models.CharField(批次号, max_length64) warehouse models.CharField(库位, max_length64, defaultA区-01) quantity models.DecimalField(库存数量, max_digits12, decimal_places3, validators[MinValueValidator(0)]) updated_at models.DateTimeField(更新时间, auto_nowTrue) class Meta: db_table stock unique_together (material, batch_no, warehouse)批次号太重要了滤清器材料一旦出质量问题要能按批次追溯到供应商、生产日期、入库单。unique_together保证同一材料同一批次同一库位只有一条库存记录每次出入库都是更新这条记录的quantity而不是插一条新记录——这是进销存系统的标准做法避免库存越算越乱。3.3 库存出入库与安全库存预警逻辑考虑到会考卫生事业单位的库存超发问题我在出入库服务里单独写了一个事务方法确保库存扣减不会因为并发请求出错。from django.db import transaction from django.db.models import F class StockService: staticmethod transaction.atomic def inbound(material_code, batch_no, quantity, warehouseA区-01): material Material.objects.select_for_update().get(codematerial_code) stock, created Stock.objects.select_for_update().get_or_create( materialmaterial, batch_nobatch_no, warehousewarehouse, defaults{quantity: quantity} ) if not created: stock.quantity F(quantity) quantity stock.save() # 写库存流水 StockLog.objects.create( materialmaterial, batch_nobatch_no, change_typeIN, quantityquantity, warehousewarehouse ) return stock staticmethod transaction.atomic def outbound(material_code, batch_no, quantity, warehouseA区-01): material Material.objects.select_for_update().get(codematerial_code) try: stock Stock.objects.select_for_update().get( materialmaterial, batch_nobatch_no, warehousewarehouse ) except Stock.DoesNotExist: raise ValueError(f库存不存在: {material_code} 批次 {batch_no}) if stock.quantity quantity: raise ValueError(库存不足出库失败) stock.quantity F(quantity) - quantity stock.save() StockLog.objects.create( materialmaterial, batch_nobatch_no, change_typeOUT, quantityquantity, warehousewarehouse ) return stock这里有两个关键点一是select_for_update()在事务里对相关行加锁并发出库时后一个请求等前一个提交后才读库存从根上避免超发二是用F(quantity) - quantity做数据库层面的原子操作避免读改写之间的竞态条件。新手最容易漏的就是这两步结果测试时单线程没问题线上并发一高库存就负数了。安全库存预警就简单了写一个定时任务或接口查所有Stock表里库存总量低于Material.safety_stock的材料生成预警记录。实际项目里我用django-crontab每天凌晨跑一次把预警结果推送给采购角色用户。4. 前端页面与接口对接从登录到库存看板前端部分我用的Vue 3组合式API Element Plus页面不多但每个页面都有讲究。这一节挑核心的几个页面讲实现思路和踩坑点。4.1 工程初始化与路由设计项目用Vite创建目录结构如下src/ ├── api/ # 接口请求封装 ├── assets/ ├── components/ # 公共组件 ├── router/ # 路由配置 ├── store/ # Pinia状态管理 ├── views/ # 页面组件 │ ├── Login.vue │ ├── Dashboard.vue │ ├── materials/ │ │ ├── MaterialList.vue │ │ └── MaterialEdit.vue │ └── stock/ │ ├── StockList.vue │ ├── Inbound.vue │ └── Outbound.vue ├── App.vue └── main.js路由配置要特别说一个坑Vue Router的history模式在正式部署时必须配合Nginx的try_files配置否则用户刷新页面就404。开发用createWebHistory没问题打包部署后如果不想折腾Nginx可以先老老实实用createWebHashHistory后面部署小节我再细说。路由守卫我写在router/index.js里核心逻辑是没有token就跳登录页有token但访问登录页就跳首页。Vue Router的beforeEach守卫提供了to和from两个路由对象比较它们就能控制页面切换。router.beforeEach((to, from, next) { const token localStorage.getItem(access_token) if (to.path /login) { token ? next(/) : next() } else { token ? next() : next(/login) } })4.2 材料列表页表格、筛选、分页与路由传参材料列表页是用户天天看的页面我做成了“顶部筛选 表格 分页”三段式。筛选区放材料编码、名称、分类三个条件表格展示编码、名称、规格、库存总量、安全库存、操作按钮分页用Element Plus的el-pagination。接口请求参数要和后端分页逻辑对齐。我后端用的是Django REST Framework的ListAPIView配合PageNumberPagination前端每次翻页或筛选都会重新调/api/materials/?searchxxcategoryFILTERpage2page_size20。这里有个典型新手问题前端是让current-page和page-size自己维护还是和URL参数双向绑定我的建议是页面状态里维护一份URL参数只做初始化和列表筛选条件同步。搜索筛选条件变化时手动把currentPage重置为1否则你停在第三页搜了个关键词结果什么都搜不到。路由传参有两种方式query?id123和params/edit/123。编辑页面我推荐用params方式因为它更语义化URL也好理解。点击编辑按钮时router.push({ name: MaterialEdit, params: { id: row.id } })编辑页里用route.params.id拿到主键再调详情接口回填表单。好处是页面刷新后URL仍然能用历史记录也清晰。4.3 权限控制与统一请求封装axios实例封装是前端基建里最值得花时间的地方。我在src/api/request.js里做了三件事请求拦截器自动加Authorization: Bearer token响应拦截器统一处理业务错误码和HTTP状态码遇到401自动跳登录页并清空本地token。import axios from axios import router from /router import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 15000 }) request.interceptors.request.use(config { const token localStorage.getItem(access_token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response response.data, error { if (error.response?.status 401) { localStorage.removeItem(access_token) localStorage.removeItem(refresh_token) router.push(/login) } ElMessage.error(error.response?.data?.detail || 请求失败) return Promise.reject(error) } ) export default request权限控制只在前端隐藏按钮是不安全的后端接口必须做权限校验。我的做法是前端根据用户角色决定是否渲染“入库/出库”按钮后端在视图类里用permission_classes强制校验角色。前端隐藏按钮是为了体验后端校验权限才是真的安全。前端还有一个文件下载的常用场景Excel导出。后端的Django视图返回StreamingHttpResponse并设置Content-Type为application/vnd.openxmlformats-officedocument.spreadsheetml.sheetContent-Disposition为attachment; filenamematerials.xlsx。前端要用responseType: blob接收再通过URL.createObjectURL生成临时下载链接。直接拿response.data当字符串处理是我见过最常见的导出失败原因。4.4 库存看板与扩展从表格到可视化和流媒体预览库存看板我放到Dashboard页面用el-card摆三四个核心指标库存总品类数、低库存预警数、近七日出入库笔数。更直观一点的可以用ECharts画一个“各类材料库存占比”的饼图和一个“近30天出入库趋势”的折线图。图表数据来自后端的统计接口前端只负责把数组数据填进图表配置。如果系统里还要放产品操作视频或材料检验视频前端播放m3u8格式的视频流也是经常遇到的需求。原生video标签不支持m3u8需要引入hls.js代码很简单import Hls from hls.js const video document.getElementById(video) if (Hls.isSupported()) { const hls new Hls() hls.loadSource(http://127.0.0.1:8000/media/videos/demo.m3u8) hls.attachMedia(video) }这和仓库管理系统本身关系不大但如果你在企业里做管理系统迟早会遇到类似的多媒体展示需求。5. 部署上线与问题排查把系统真正跑起来开发环境跑通只是第一步真正把系统部署到公司服务器上才是考验。我见过太多项目在本地一切正常一上服务器就各种诡异问题这一节把高频坑一次说透。5.1 用 Waitress Nginx 部署 Django 后端Windows服务器上部署Django最稳的组合是Waitress一个纯Python的WSGI服务器加Nginx负责静态文件和反向代理。Waitress虽然不像Gunicorn那样在Linux上性能那么猛但在Windows上能稳定运行不挑环境对一个中小型仓库管理系统完全够用。启动命令很简单waitress-serve --listen0.0.0.0:8000 warehouse.wsgi:application生产环境有几个必须改的配置把DEBUG设为FalseALLOWED_HOSTS加上服务器IP或域名用python manage.py collectstatic收集静态文件数据库连接换成生产库把SECRET_KEY换成环境变量读取。DEBUGFalse后Django不再帮你处理静态文件所以需要Nginx来托管静态资源。Nginx的关键配置如下server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /opt/warehouse/dist; index index.html; try_files $uri $uri/ /index.html; # 解决history模式刷新404 } # 后端API反向代理 location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Django静态文件 location /static/ { alias /opt/warehouse/staticfiles/; } }这个配置有个细节容易坑到人location /的try_files配置是为了配合Vue Router的history模式。如果没有try_files $uri $uri/ /index.html;用户点击前端路由没问题但一刷新浏览器就会向Nginx请求/materials/listNginx找不到这个文件直接返回404。加上之后所有匹配不到的文件都回退到index.html由前端路由接管。5.2 跨域、编码、时区等高频问题速查表以下是我在这个项目里遇到的真实问题整理成一张速查表建议直接收藏问题现象根本原因解决方案前端请求接口报CORS错误前后端不同域名或端口开发环境用Vite代理生产环境配Nginx反代后端加django-cors-headers数据库中文乱码MySQL建库时字符集不是utf8mb4建库时指定CREATE DATABASE warehouse DEFAULT CHARACTER SET utf8mb4时间差了8小时Django时区配置不对TIME_ZONE Asia/ShanghaiUSE_TZ True打包后页面样式丢失静态文件路径是绝对路径打包时base: ./或Nginx反代静态文件接口返回的Decimal字段前端显示异常JSON序列化Decimal类型问题用DRF的JSONEncoder或前端统一做Number转换Vue列表数据更新但页面不刷新对象新增属性不具备响应性用this.$set或直接替换整个对象el-table分页后勾选状态错乱表格的row-key未设置给el-table加:row-keyrow row.id5.3 我在这个项目里踩过的三个“真实”坑第一个坑是批次号编码校验。最开始设计入库单时批次号我直接让人工输入结果一周内出现了三笔重复批次入库库存数据彻底对不上。后来加了一个规则批次号必须由供应商编码生产日期流水号组成前端正则校验格式后端序列化器里用validate_batch_no方法二次校验。前端校验是为了用户体验后端校验才是真正的防线永远不要在接口层相信前端传来的任何数据。第二个坑是库存扣减没加事务。我做Outbound接口时图省事先查库存再判断再更新完全没有事务包裹。后来用并发工具一压测两个请求同时读到库存为10同时判断“够”同时各自扣了8库存直接变负数。这是一个教科书级别的并发安全问题解决办法就是前面写的transaction.atomic加select_for_update()。第三个坑是Excel导出时Content-Disposition里的文件名中文乱码。直接写filename材料清单.xlsx在Windows浏览器里会变成乱码需要做URL编码处理from urllib.parse import quote filename quote(材料清单.xlsx) response[Content-Disposition] fattachment; filename*UTF-8{filename}这个细节如果不在真实环境里被用户吐槽你可能根本不会注意到。最后再分享一个方法论层面的体会做这类管理系统不要一上来就埋头写代码。先用一两天时间把业务方真实的“痛点清单”拉出来——他们是觉得Excel查询慢还是对账对不上还是经常漏采购——然后倒推系统要做什么功能、哪些功能必须做深、哪些功能做个样子就行。仓库管理系统的核心永远是“账实一致”四个字技术只是手段把数据搞准了系统就成功了一半。后面如果你想扩展可以参考一下同类的Django项目比如基于SpringBoot Vue的管理系统、多媒体资源管理系统的做法把文件管理、流程审批这些模块逐步加进来系统的生命力会更强。
返回列表