资讯动态

Python+Flask+Vue实现物资保养与智能人员调度系统

发布时间:2026/10/13 16:45:01 来源:尧图企业网站定制
做这套系统之前我一直在想一个问题场地里那么多装备物资台账谁来做、保养什么时候该做、做完由谁去签字确认靠Excel和微信群到底能撑多久。后来发现基层单位真正需要的并不是一个功能堆得满满的“大平台”而是一个能跑在局域网里、打开浏览器就能用的轻量系统把物资保养计划和人员调度这两件事串起来。我用Python做后端Flask提供接口Vue写前端做了一套“物资保养管理系统”加“基层智能化人员调度系统”。听起来像两个系统实际是放在同一个项目里的两个业务模块。管理员可以录入物资、设置保养周期系统按周期自动生成保养任务再根据任务关键字与人员技能标签的相似度给出智能调度建议。这个思路对做过Flask后台、写过Vue页面、又想练手“匹配算法”的朋友很有参考价值也是基层小团队做信息化改造时很实用的一套架构。1. 为什么偏偏用 Python Flask Vue1.1 Flask在后台类系统里到底香在哪但凡做过几个后台管理项目都会发现大多数业务系统的形态很相似登录、列表、表单、查询、导出再加几个统计图表。这类系统对Web框架的要求就是“轻、快、稳”。Flask在这类场景里简直是为基层信息化量身定做的启动一个服务只需要几行代码路由写起来直白也没有那么多强制约定。有位同事问我为什么不用DjangoDjango当然能扛更大项目它的ORM、Admin后台、命令工具都是顶配。但正因为顶配太多在作培训时它会连环“绑手”——用户注册模块自带大而全权限体系如果你不熟悉改造反而更少了手脚。Flask更像是一个“百宝箱”需要ORM就装Flask-SQLAlchemy需要跨域就装Flask-CORS需要定时任务就装APScheduler。每个组件都是你亲手组装上去的出了问题你比框架更清楚来龙去脉。另外基层系统通常部署在一台配置不算高的Windows服务器上跑Nginx加MySQL有时候都显得重。Flask配合SQLite一个进程就能跑起来资源占用小维护成本也低这套组合在物资保养这类数据量不大的系统里非常合适。1.2 Vue让前端不至于变成“答辩现场”早期很多Flask项目是后端用Jinja2模板渲染页面页面一多模板继承、表单验证、AJAX穿插在一起改起来很痛苦。后来我改用前后端分离方案前端直接上Vue 3。用Vue最直接的感受是页面状态好管理了。物资列表、保养任务、人员调度看板每一块都是一个独立组件物料列表组件只要管好它自己的数据请求和交互逻辑不会互相干扰。配Element Plus后表格、表单、弹窗、分页全都是现成组件界面不会出现那种“一眼答辩”的孤边样式。关于Vue安装环境配置我给新人的建议是不要自己手写webpack配置直接用Vite。Vite启动快热更新也快对Vue单文件组件的支持非常顺畅。配合VueRouter做多页面跳转用Axios统一处理网络请求再在开发环境用Vite的proxy把请求转发到Flask端口整个前后端联调流程相当顺滑。1.3 这套架构为什么会适合“基层小系统”基层单位的信息化系统有几个特点用户人数少并发低业务逻辑不复杂但流程固定上线的机器环境不稳定后续改动需求很多。这套“Flask接口 Vue前端 SQLite存储”方案单机即可运行也可以随业务量升级成MySQL前后端完全分离前端页面打包成静态文件后可以直接让Flask托管真正做到“一个服务端跑完所有”。更重要的是开发效率。Flask路由加SQLAlchemy模型一个下午就能把后端接口写明白Vue前端用现成组件库页面也就一两天的事情。对想快速交付、又不想被复杂工程折腾的人来说这种轻架构就是最有性价比的选择。2. 把物资保养和人员调度变成一条流水线2.1 资产台账和保养计划怎么建做物资保养第一件事是梳理“哪些东西要保养多久保养一次”。我的做法是先建三张核心表物资表、保养标准表、保养任务表。物资表是关键资产的身份信息包括编号、名称、分类、存放位置、当前状态和最近一次保养日期。保养标准表用于界定不同物资的保养属性比如某台发电机每30天要做一次例行保养技能标签是“机械检查”某种检测仪器每15天要做一次校准技能标签是“仪表校准”。系统每次生成任务就根据“上次保养日期 保养周期”计算出下一次应该做保养的日期。这里有个很实际的细节保养周期不能全部按天数写死。有的设备按季度有的按运行时长有的需要季节性换油。我建议在保养标准表里加一个cycle_type字段区分“按天”“按周”“按月”甚至自定义规则。例如季度保养就是interval_days90一个月保养就是interval_days30这样生成任务时逻辑清晰也不需要在代码里写一堆魔法数字。保养任务生成的逻辑也很直接每天启动系统时检查一次凡是“计划日期小于等于今天、状态还不是已完成”的保养标准就自动生成一条任务。任务会带着物资信息和技能标签要求进入待调度队列等待人员指派。2.2 “智能化调度”到底智能在哪基层排班最普遍的现象就是“挑人头”活来了管理员在群里一圈谁能干谁上。能干的人天天忙不会的人一直闲最后连“谁做过什么、哪些人擅长干什么”都说不清。我们把调度智能化的核心放在“任务技能标签与人员技能标签的自动匹配”上。这里借鉴了关键词相似度匹配的思路。我在用户表里给每个维修保养人员维护了一组技能标签例如“电工”“机械维修”“液压系统”“仪表校准”“底盘保养”。任务生成时也会提取一组技能标签比如“发电机保养”系统把任务标签和人员标签放到同一个向量空间里用TF-IDF加余弦相似度计算每条任务与每位员工的匹配度。为了不让调度建议变成“拍脑袋”我还加了三个权重因子匹配度得分占大头近期任务数作为负载均衡因子历史完成质量作小幅修正。这里“历史完成质量”不是复杂算法就是对已完成任务的验收评分动态取平均分数高的人在得分相当的情况下优先推荐。计算公式可以表示成推荐得分 0.6 × 技能标签相似度 0.3 × (1 - 当前待办任务数 / 最大任务数) 0.1 × 历史平均验收评分这样调度建议比较全面能干的人优先排但不会让他一个人扛所有任务技能相似但更空闲、历史完成度更好的人同样有机会。这套逻辑就是系统里所谓的“智能推荐”底层并不神秘就是特征加权加排序。2.3 数据表设计里的学问模型建得好业务逻辑就顺。我实际建表时把核心字段控制得很精简避免过度设计表关键字段说明materialsid, code, name, category, location, status, last_maintenance_date物资基本信息maintenance_standardsid, material_id, cycle_type, interval_days, skill_tags, description保养规则skill_tags字段存逗号分隔标签maintenance_tasksid, standard_id, material_id, required_skill, plan_date, status, assignee_id, score每次生成的保养任务usersid, username, name, skill_tags, workload_count, avg_score人员及其技能标签需要注意skill_tags如果只存逗号分隔字符串查询和更新会有一点麻烦但在这个规模下就图个简单。更新时用逗号重新join匹配时再根据任务需求拆开没有引入标签表数据库和维护成本都更低。如果未来标签体系复杂了再拆成“人员标签关联表”也不迟。3. 实操落地从零搭建这套系统3.1 后端项目结构怎么组织我习惯把Flask项目分成应用工厂加蓝图的模式这样每增加一个模块不会把代码全堆在app.py里。目录结构大致如下flask-app/ ├── app/ │ ├── __init__.py # 创建app注册蓝图 │ ├── models.py # SQLAlchemy模型 │ ├── api/ │ │ ├── materials.py # 物资接口 │ │ ├── tasks.py # 保养任务接口 │ │ └── dispatch.py # 调度匹配接口 │ ├── services/ │ │ └── matcher.py # 相似度匹配算法 │ └── utils.py # 日期计算等公共函数 ├── requirements.txt └── run.pyrequirements.txt里面最核心的依赖是flask、flask-sqlalchemy、flask-cors、jieba、scikit-learn、numpy。日期计算我直接用标准库datetime没有一个复杂的包。3.2 物资模型和任务生成的关键代码先看物资模型的写法from flask_sqlalchemy import SQLAlchemy from datetime import date db SQLAlchemy() class Material(db.Model): __tablename__ materials id db.Column(db.Integer, primary_keyTrue) code db.Column(db.String(64), uniqueTrue, indexTrue) name db.Column(db.String(128)) category db.Column(db.String(64)) location db.Column(db.String(128)) status db.Column(db.String(20), default正常) last_maintenance_date db.Column(db.Date, defaultdate.today) def __repr__(self): return fMaterial {self.code} {self.name}任务生成逻辑封装成一个函数每日定时触发def generate_daily_tasks(): today date.today() standards MaintenanceStandard.query.all() created 0 for std in standards: last_date std.material.last_maintenance_date or std.created_at.date() next_date add_cycle(last_date, std.cycle_type, std.interval_days) if next_date today: exists MaintenanceTask.query.filter_by( standard_idstd.id, status待处理 ).first() if not exists: task MaintenanceTask( standard_idstd.id, material_idstd.material_id, required_skillstd.skill_tags, plan_datenext_date, status待处理 ) db.session.add(task) created 1 db.session.commit() return createdadd_cycle函数是根据周期类型做日期加减def add_cycle(base_date, cycle_type, interval): if cycle_type day: return base_date timedelta(daysinterval) if cycle_type week: return base_date timedelta(weeksinterval) if cycle_type month: # 按月份加减需要处理月末 month base_date.month - 1 interval year base_date.year month // 12 month month % 12 1 day min(base_date.day, [31, 29 if year % 4 0 and year % 100 ! 0 or year % 400 0 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1]) return date(year, month, day)这段代码现在看着简单但“按月份加减”那里我踩过坑比如1月31日加一个月如果不做月底收敛会直接报“day out of range”。现在这个写法能处理月末自动收缩生成任务日期不会出错。3.3 调度匹配算法的核心实现匹配算法的核心是TF-IDF加余弦相似度。先看完整实现import jieba import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer def cosine_similarity(a, b): dot np.dot(a, b) norm_a np.linalg.norm(a) norm_b np.linalg.norm(b) if norm_a 0 or norm_b 0: return 0.0 return float(dot / (norm_a * norm_b)) def tokenizer(text): return jieba.lcut(text) def calc_match_score(task_text, user_tags): vectorizer TfidfVectorizer(tokenizertokenizer, token_patternNone) vectors vectorizer.fit_transform([task_text] user_tags).toarray() task_vec vectors[0] scores [] for user_vec in vectors[1:]: scores.append(cosine_similarity(task_vec, user_vec)) return scores这里的用法是把“发电机组例行保养、机油滤芯检查”这样的任务文本和每个用户的技能标签字符串放在一起做TF-IDF向量化。TF-IDF能体现“机油”“滤芯”这类词在不同标签里的辨识度余弦相似度衡量任务和人员标签在向量空间里的接近程度。实际使用时还要做加权修正我在调度接口里会这样综合def recommend_users(task, candidates, max_load5): task_text f{task.required_skill} {task.material.name} {task.material.category} base_scores calc_match_score(task_text, [u.skill_tags for u in candidates]) results [] for user, base in zip(candidates, base_scores): load_factor 1 - user.workload_count / max_load skill_factor base quality_factor user.avg_score if user.avg_score else 0.8 total 0.6 * skill_factor 0.3 * load_factor 0.1 * quality_factor results.append((user.username, round(total, 4), round(base, 4), user.workload_count, user.avg_score)) results.sort(keylambda x: x[1], reverseTrue) return results[:5]这套逻辑跑起来效果很直观一个“液压系统保养”的任务推荐人选时技能标签里带“液压”的人得分会明显靠前如果带“液压”标签的人手上已经压了5个任务得分就会被负载因子拉低给其他技能相近、任务更少的人机会。这套调度机制兼顾了技术匹配和工作量均衡。还有一点如果是中文短文本纯TF-IDF会遇到分词不稳定、匹配度普遍偏低的问题。可以先用jieba.analyse提取任务文本的关键词把提取出的topN关键词拼接成新的文本再去做匹配。我实际测试过匹配区分度会比直接用原文好一些。3.4 Vue前端怎么把这个流程做成页面前端核心就三块物资台账页面、任务生成页面、调度推荐页面。Vue路由比较简单import { createRouter, createWebHistory } from vue-router import MaterialList from ../views/MaterialList.vue import TaskBoard from ../views/TaskBoard.vue import DispatchPage from ../views/DispatchPage.vue const routes [ { path: /, component: MaterialList }, { path: /tasks, component: TaskBoard }, { path: /dispatch, component: DispatchPage } ] const router createRouter({ history: createWebHistory(), routes })Axios请求统一封装成一个模块import axios from axios const http axios.create({ baseURL: /api, timeout: 5000 }) export default http调度推荐页面是整个系统里交互最重的地方。页面加载任务列表后点击任务的“推荐人员”按钮就调用后端的dispatch接口返回top5候选人前端用表格展示推荐得分、技能标签匹配度、当前待办任务数、历史平均评分。管理员可以一键选中推荐人并提交指派。开发调试时Vite的proxy配置需要这样写才能让前端请求顺利转发到Flask的5000端口server: { proxy: { /api: { target: http://127.0.0.1:5000, changeOrigin: true } } }这个配置非常关键不然前端页面始终请求5000端口会跨域报错。3.5 部署到服务器的坑附件路径和运行位置项目开发完成后部署到局域网服务器这里有一个非常常见的坑附件路径错误。很多Flask项目会把上传目录写成相对路径app.config[UPLOAD_FOLDER] ./uploads开发时没问题因为启动目录就是项目目录。部署时如果把启动命令放在其他目录比如用nssm把Flask注册成Windows服务或者用systemd启动脚本工作目录一变上传的文件就会跑到莫名其妙的地方还可能因为目录不存在直接报错。正确做法是固定基于项目根目录的绝对路径import os BASE_DIR os.path.abspath(os.path.dirname(__file__)) UPLOAD_FOLDER os.path.join(BASE_DIR, uploads) os.makedirs(UPLOAD_FOLDER, exist_okTrue)部署Windows服务器时启动命令写成cd C:\project\flask-app python run.py --host 0.0.0.0 --port 5000这样工作目录固定附件路径也不会飘。4. 开发中常见的几个坑和排查思路4.1 前端请求一直404或跨域报错Vue开发服务器跑到5173Flask跑到5000直接请求肯定跨域。两种情况分别处理。开发环境下优先在Vite里配proxy不需要后端处理跨域。如果实在要直连Flask需要开Flask-CORSfrom flask_cors import CORS CORS(app)生产环境最好是把前端打包后的dist目录直接交给Flask托管Flask增加一个静态文件路由和一个回退路由VueRouter的history模式才不会有404问题。4.2 SQLite的databasse is locked系统里面有定时生成任务和多个用户同时操作时SQLite并发写容易出现“database is locked”。SQLAlchemy连接SQLite时可以加上连接参数规避app.config[SQLALCHEMY_ENGINE_OPTIONS] { connect_args: {check_same_thread: False, timeout: 15} }这会增加SQLite等待锁的时长避免短并发写直接报错。但别指望SQLite扛住大型并发基层系统同时在线几个人是完全没问题的。4.3 任务生成时间不准系统上线后发现21点生成的保养任务到第二天早上才出现原因是定时任务调度器使用了单线程执行耗时就在那段。后来白天实际验证才明白是服务器时区设置与数据库存UTC时间造成换算偏差。解决办法所有日期统一用本地时间生成数据库字段直接存本地日期。Flask里尽量少用datetime.now(timezone.utc)做业务逻辑因为和底层管理员认知不一致。4.4 相似度匹配全是0分第一次用TfidfVectorizer匹配中文时我发现很多任务和人员标签的得分是0因为直接分词的结果不理想。建议先用jieba.analyse.extract_tags把任务文本提取关键词再放向量器里算。我最后实现是先清洗文本去掉一堆口号性质的无意义词只保留动作加对象比如“检查”“保养”“维修”“发电机”“液压”这些匹配效果一下子就好了。4.5 Vue 3版本和Element Plus不兼容用Vite创建Vue 3项目后安装Element Plus页面一直空白控制台报找不到组件。大多是版本不一致引起的。安装方式统一用npm install vue3 element-plus element-plus/icons-vue axios vue-router4这样确保VueRouter4配Vue3Element Plus也用最新版就不会出现版本错位。5. 项目还能往哪些方向长这套系统的骨架搭完后扩展方向比我预期的还多。第一个值得做的是保养统计报表。现在数据都在库里有日期、人员、评分拿来做月度完成率、人员工作量、保养成本趋势都很方便。第二个实用功能是到期提醒可以在系统里加一个简单的待办角标也可以对接企业微信机器人让管理员每天早上收到今日保养清单。我后面还计划把“人员技能标签”梳理得更规范变成可单独维护的标签库。当标签变成数据字典之后任务匹配就能做更细的筛选比如“高空作业证”“低压电工证”这种资质条件直接变成硬性门槛过滤比单纯相似度匹配更可靠。最后特别想提示一点这套系统本质上不是“上了一个软件”而是把原来模糊的管理流程固化成线上流程。做基层信息系统真正难的也不是代码而是怎么把物资保养的周期规则、人员调度的人工经验转成系统能执行的逻辑。先跟实际业务人员聊清楚再来写代码后面会少走很多弯路。这套Flask加Vue的轻量架构没有复杂到吓人的概念每加一个模块都是独立的想继续完善也很容易。我建议所有想练手Flask和Vue整合的朋友都拿类似“物资保养加人员调度”这种主题来实操一遍做完你会对整个前后端分离开发流程有更整体的把握。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑