ARTICLE DETAIL

资讯详情

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

基于Python+Vue的项目分配与进度管理系统设计与实现

基于Python+Vue的项目分配与进度管理系统设计与实现 团队里每天最热闹的群消息永远是“这个任务做到哪了”“谁有空接下这个需求”“项目延期了你知道吗”。做过项目管理的人应该都有同感任务分配靠口头、进度同步靠会议、延期风险靠事后才知道。我去年用 Python Vue 这套前后端分离方案给一家做软件外包的公司从零搭了一套项目分配与进度管理系统从需求梳理、数据库设计、接口开发到前端页面、部署上线完整走了一遍。这篇文章就把这套系统的设计与实现过程原原本本拆开讲包括架构选型原因、数据表结构、核心 API 设计、前端页面的实现思路以及联调和上线时踩过的坑。无论你是准备做毕业设计、给团队做内部工具还是单纯想学 Python 后端和 Vue 前端怎么配合这篇都能给你一条可以直接复刻的路径。1. 需求分析与整体架构设计1.1 管理场景中的核心痛点动手写代码之前我先把客户那边的实际工作流捋了一遍。这个公司大概四十多人项目经理有七八个开发、测试、设计加起来三十多号人。他们之前的流程是项目经理接到单子在群里喊一嗓子谁有空然后在 Excel 里登记一下后续进度靠每周例会口头汇报。这种模式的问题非常典型。第一项目分配没有依据谁手上压了几个活、还剩多少工作量全靠个人印象经常出现一个人同时被塞了三四个紧急项目的情况。第二进度信息不透明项目经理想知道某个项目到哪一步了得私聊当事人一个项目问一遍七八个项目问下来半天就没了。第三延期没有预警往往是到了交付日期才发现做不完连调整资源的时间都没有。所以这套系统的核心需求其实很明确把“项目分配”和“进度管理”这两件事线上化。项目分配要能展示每个成员当前的负载情况负责人能根据工作量合理派活进度管理要能让每个成员随时更新自己名下任务的状态管理者实时看到整体进度快到期没完成的自动高亮提醒。1.2 为什么选 Python Vue 这套技术组合技术选型的时候也犹豫过要不要用 Spring Boot Vue毕竟那套在企业里面更常见。但考虑到这个团队的情况我最终选了 Python 后端。原因有几个。首先是开发效率。团队里没有专职 Java 后端倒是有人写过 Python 脚本做数据处理。Python 的 Flask 框架写一个 RESTful 接口几行代码就能搞定模型定义、数据库迁移都有现成工具对一个小型内部管理系统来说开发周期能压到很短。其次是后续维护门槛低Python 语法简单将来团队里任何人接手都能快速看懂。前端选 Vue 则更直接。Vue 的学习曲线比 React 平缓模板语法接近原生 HTML对于团队里主要以写页面为主、没有系统学过前端框架的人来说上手成本最低。而且 Vue 的生态很成熟Element UI 组件库直接提供表格、表单、日期选择器、进度条这些管理后台常用组件开发效率非常可观。这套组合我在实际开发中体会到的一个额外好处是前后端完全分离。后端只出 JSON 接口前端只负责页面渲染两边可以并行开发。对于我一个人同时写前后端的情况分离架构也让我调试起来更轻松——接口写好了先用 Postman 测前端页面出问题了能快速定位是渲染问题还是接口问题。1.3 系统模块划分与业务流整个系统按业务拆成四个核心模块项目管理项目的创建、编辑、归档项目基本信息名称、周期、优先级、描述项目分配负责人将项目下拆分成多个任务指定给具体成员设置计划开始和结束日期进度管理成员更新任务状态未开始、进行中、已完成填写进度百分比系统自动汇总项目整体进度系统管理用户管理、角色权限管理员、项目经理、普通成员、个人负载看板业务流程是这样的管理员登录后进入项目列表页面创建新项目并指定项目经理项目经理进入项目详情页把项目拆成若干个任务分配给对应的开发测试人员成员登录后在自己名下能看到被分配的任务列表点击进入可以编辑进度百分比、更新状态、填写备注项目经理和管理员通过看板页面查看所有项目的实时进度和成员的负载情况。这套流程定下来之后数据表结构、接口设计、前端页面路由就都有了清晰的依据。接下来我分别说后端和前端的具体实现。2. 后端Python 接口层与数据模型设计2.1 框架选型Flask vs Django后端框架我纠结过 Flask 和 Django。最后选了 Flask加 SQLAlchemy 做 ORM。Django 的优势是“全家桶”自带 Admin 后台、认证系统、ORM但这些优势在这个项目里其实用不太上。Django 的项目结构相对固化对新手来说理解成本高一些而且它内置的东西多跑起来占用的资源也大。Flask 恰好相反核心精简到只有一个路由分发和请求响应的骨架数据库、表单校验这些都可以按需引入。对于这套系统来说接口大概二十多个Flask 的组织方式完全够用而且每个文件的逻辑都一目了然。补充一点数据库我用的是 MySQL。虽然 SQLite 开发时更方便但生产环境多个人同时使用SQLite 的并发写入瓶颈很致命。MySQL 配合 SQLAlchemy 连接池几十个人的内部系统稳得很。2.2 数据库表结构与字段解析这套系统一共设计了六张核心表用户表、项目表、任务表、项目成员关联表、操作日志表、字典表。这里重点讲前四张。用户表字段设计class User(db.Model): __tablename__ sys_user id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(64), uniqueTrue, nullableFalse) password_hash db.Column(db.String(128), nullableFalse) real_name db.Column(db.String(32), nullableFalse) role db.Column(db.String(16), defaultmember) # admin / manager / member department db.Column(db.String(32)) status db.Column(db.SmallInteger, default1) # 1启用 0禁用 created_at db.Column(db.DateTime, defaultdatetime.now)role 字段直接存字符串只有三个值不需要单独立字典表。很多系统会把角色做成单独的表再用关联表绑定那是对权限体系特别复杂的场景。这个系统里角色只影响前端菜单显示和后端接口校验一个字段就够了。项目表和任务表是核心中的核心。任务表我格外加了几个字段class Task(db.Model): __tablename__ project_task id db.Column(db.Integer, primary_keyTrue) project_id db.Column(db.Integer, db.ForeignKey(project.id)) title db.Column(db.String(128), nullableFalse) assignee_id db.Column(db.Integer, db.ForeignKey(sys_user.id)) status db.Column(db.String(16), defaultpending) # pending/doing/done progress db.Column(db.SmallInteger, default0) # 0-100 plan_start db.Column(db.Date) plan_end db.Column(db.Date) actual_start db.Column(db.Date) actual_end db.Column(db.Date) priority db.Column(db.String(8), defaultmedium) # high/medium/low description db.Column(db.Text) updated_at db.Column(db.DateTime, defaultdatetime.now, onupdatedatetime.now)progress 字段是这套系统计算进度的关键。任务进度由成员手动填百分比每次修改记录 updated_at 时间戳。项目整体进度不单独存字段而是通过查询所有任务的 progress 取平均值计算出来这样不会出现“任务都改完了但总进度没刷新”的脏数据问题。项目成员关联表解决的是“一个项目多个人参与”的多对多关系。在项目管理里一个项目除了项目经理还会牵扯到测试、UI 等角色需要一张关联表记录谁参与了哪个项目。class ProjectMember(db.Model): __tablename__ project_member id db.Column(db.Integer, primary_keyTrue) project_id db.Column(db.Integer, db.ForeignKey(project.id)) user_id db.Column(db.Integer, db.ForeignKey(sys_user.id)) member_role db.Column(db.String(32)) # 项目内角色 joined_at db.Column(db.DateTime, defaultdatetime.now)2.3 核心 API 设计与状态流转接口设计遵循 RESTful 风格资源用名词复数操作用 HTTP 方法区分。我列一下主要的接口功能方法路径说明用户登录POST/api/auth/login返回 token项目列表GET/api/projects支持按状态、关键字筛选创建项目POST/api/projects管理员/项目经理项目详情GET/api/projects/{id}含任务列表和成员列表任务列表GET/api/tasks按 assignee/status/project 筛选创建任务POST/api/tasks给指定成员派活更新进度PATCH/api/tasks/{id}/progress成员更新进度和状态成员负载GET/api/users/load各成员名下任务数和进度状态流转这一块是容易出问题的。任务的 status 和 progress 必须是联动关系progress 为 0 时状态只能是 pendingprogress 在 1 到 99 之间时状态是 doingprogress 到 100 时状态自动变为 done。这个逻辑我在后端更新接口里做了强制校验前端也做了对应交互限制。task_bp.route(/int:task_id/progress, methods[PATCH]) def update_progress(task_id): task Task.query.get_or_404(task_id) data request.get_json() new_progress data.get(progress) if new_progress is None or not (0 new_progress 100): return jsonify({msg: 进度值必须在0-100之间}), 400 task.progress new_progress if new_progress 0: task.status pending elif new_progress 100: task.status done task.actual_end date.today() else: task.status doing if not task.actual_start: task.actual_start date.today() task.updated_at datetime.now() db.session.commit() return jsonify({msg: ok, data: task_to_dict(task)})这段代码实现了三个关键逻辑第一是进度和状态的自动联动第二是任务实际开始时间的自动记录第三是完成时自动写入实际结束日期。这些时间数据后面用来做项目延期分析和成员效率统计非常有用。2.4 认证方案与权限控制登录认证我用了 JWT。用户登录成功后后端用 PyJWT 生成一个带有效期的 token里面加密用户 id 和角色信息。前端每次请求在 header 里带上Authorization: Bearer token后端写一个装饰器来解析 token 并校验接口权限。def token_required(f): wraps(f) def decorated(*args, **kwargs): token request.headers.get(Authorization) if not token or not token.startswith(Bearer ): return jsonify({msg: 未登录或token缺失}), 401 try: payload jwt.decode( token.split( )[1], app.config[SECRET_KEY], algorithms[HS256] ) current_user User.query.get(payload[uid]) except jwt.ExpiredSignatureError: return jsonify({msg: 登录已过期}), 401 except Exception: return jsonify({msg: 无效token}), 401 return f(current_user, *args, **kwargs) return decorated权限控制这边我分了三层。第一层是页面路由控制管理员能看到用户管理菜单普通成员看不到第二层是数据范围控制普通成员调用任务列表接口时后端只返回 assignee 是自己的任务防止越权看到别人的任务第三层是操作控制只有项目经理和管理员能调创建项目、分配任务这类写接口。三层下来基本覆盖了内部系统的权限需求。3. 前端Vue 页面架构与交互实现3.1 工程初始化与目录规划前端我直接用 Vue CLI 初始化项目组件库选的 Element UIHTTP 请求用 axios。这里有一点我要特别说明Vue 的版本用的是 2.x不是 3.x。虽然 Vue 3 已经是主流但 Element UI 对 Vue 3 的适配版本是 Element Plus生态和文档完整性在当时还不如 Element UI 成熟。对一个内部管理系统来说稳定大于追新。目录结构我按业务模块划分src/ ├── api/ # 接口请求模块 │ ├── auth.js │ ├── project.js │ ├── task.js │ └── user.js ├── router/ # 路由配置 ├── store/ # Vuex 状态管理 ├── views/ # 页面组件 │ ├── Login.vue │ ├── Dashboard.vue # 总览看板 │ ├── ProjectList.vue │ ├── ProjectDetail.vue │ ├── TaskBoard.vue │ └── UserManage.vue ├── components/ # 公共组件 └── utils/ # 工具函数axios封装等3.2 登录鉴权与路由守卫登录页的逻辑不复杂表单校验、调登录接口、拿到 token 后存到 Vuex 和 localStorage、跳转到首页。真正的重点是路由守卫。没有登录的人直接访问/dashboard不能让他看到页面而是要重定向到登录页。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { return token ? next(/dashboard) : next() } if (!token) { return next(/login) } // 动态路由权限过滤在meta里标记角色 if (to.meta.roles !to.meta.roles.includes(store.state.user.role)) { return next(/dashboard) } next() })axios 的拦截器也要配合好。请求拦截器统一在 header 里加上 token响应拦截器遇到 401 状态码自动清除本地 token 并跳回登录页。这一步不做的话token 过期以后用户点任何按钮都是报错体验非常差。service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) service.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(error) } )3.3 看板式进度页面实现思路进度管理页面是整个系统里交互最重的一块。我采用了“看板”的形式任务按状态分成三列未开始、进行中、已完成卡片展示任务标题、负责人、优先级、截止日期和进度条。看板实现用 Element UI 的拖拽不太顺手我直接用了原生 HTML5 拖拽事件。卡片设置draggabletrue拖拽结束触发事件后调后端接口更新任务状态整体代码在四十行左右。比引入 SortableJS 这种库更轻量也不需要考虑 Vue 和第三方库的版本兼容问题。进度条部分用的是 Element UI 的el-progress组件。卡片上展示进度百分比颜色根据数值变化低于 50 显示蓝色50 到 90 显示黄色90 以上还没完成就显示红色。红黄蓝三色让管理者一眼就能看出哪些任务快到期了还没收尾。这里有一个交互细节值得单独说。看板上直接拖拽任务更新状态确实方便但进度百分比不会跟着变。所以我在卡片上保留了一个“更新进度”的弹出框点开来可以输入百分比、填写备注保存后调PATCH /api/tasks/{id}/progress接口。拖拽改状态、弹框填百分比两条链路互不干扰逻辑清晰。3.4 Vuex 管理全局状态Vuex 在这个项目里主要管理三类数据当前登录用户信息、项目列表的筛选条件、任务看板的全局状态。用得最多的场景是跨页面共享用户信息。用户登录成功后我把用户对象包含 id、姓名、角色存进 Vuex。导航栏的“当前用户”显示、路由守卫里的角色判断、还有 API 请求里偶尔要带的用户参数都直接从 store 里取不用每次都重新调接口。模块划分上我用了一个简单的方式const store new Vuex.Store({ modules: { user: userStore, project: projectStore } })userStore 里存登录 token 和用户信息projectStore 里存当前选中的项目、项目列表的筛选项。筛选条件存 Vuex 而不是组件本地 data好处是用户从列表页进入详情页再返回时之前选的筛选条件还在不会因为组件销毁而丢失。4. 核心功能模块实操与联调细节4.1 项目分配模块从创建项目到派发任务项目分配是整个系统里业务流程最长的一个功能模块。我从真实的操作路径讲实现。第一步管理员点击“新建项目”弹出表单填写项目名称、选择项目经理、设置计划周期、选择优先级。提交后调POST /api/projects接口。这里我先只保存项目主信息不涉及成员和任务。第二步进入项目详情页在“成员管理”选项卡里添加参与人员。添加成员时后端会检查这个成员是否已经在项目里避免重复添加。项目成员是后续任务分配给谁的限制条件我做了约束只能选择该项目下的成员作为任务负责人。第三步项目管理器中创建任务最关键的是选择负责人和设置计划时间。计划开始时间和结束时间我用 Element UI 的el-date-picker日期范围选择器限制结束日期不能早于开始日期。这部分校验前端做了后端的 schema 校验也做了一遍双保险。派发任务的时候还做了一个负载提示选择负责人时下拉框的选项文字会显示该成员当前名下未完成任务的数量。比如“张三3个进行中”这样项目经理派活的时候能直观看到谁手上任务多避免“能者多劳”变成“能者过劳”。4.2 项目整体进度计算与延期预警项目整体进度不单独存储每次前端请求详情接口的时候后端实时计算。计算逻辑是所有任务 progress 的平均值四舍五入取整。def calc_project_progress(tasks): if not tasks: return 0 total sum(t.progress for t in tasks) return round(total / len(tasks))延期预警这个需求是客户特别强调的。实现方式是后端在返回任务列表时对每个任务计算一个is_overdue布尔值。判断逻辑任务状态不是 done并且当前日期晚于计划结束日期就标记为延期。后端算好直接返回布尔值前端拿到以后在卡片右上角渲染一个红色角标列表页筛选项里也支持“只看逾期任务”。这个计算放后端而不是前端是因为前端时钟不可控用户本地时间改乱了会影响判断。后端统一取服务器时间结果才可靠。4.3 个人工作台成员视角的待办与负载普通成员登录后看到的主页不是项目看板是一个个人工作台。这个页面展示两个核心内容我名下的所有未完成任务列表以及我的负载统计。负载统计这块我踩过一个设计坑。最初只统计“进行中任务数”后来发现这个指标没有考虑任务规模和紧急程度一个进行中的小任务和一个进行中的大活权重完全一样。后来我调整为列表展示我名下的任务总数、进行中数量、本周到期数量、逾期数量四个数字配合饼图展示任务状态分布。四个数字一摆负载情况就立体多了。个人工作台的任务列表支持按项目分组显示每个分组下面能看到该项目里我负责的任务旁边标记着截止日期倒计时。距离截止日期不足 3 天的任务倒计时数字会变成红色这也是前端做的纯展示逻辑不涉及后端计算。4.4 前后端联调字段对齐与接口契约前后端联调是整个开发过程里最耗时也最容易出问题的阶段。最常见的坑就是字段名对不上。后端习惯用snake_case比如plan_start、real_name前端 JavaScript 习惯用camelCase比如planStart、realName。如果前后端没有提前约定好命名规范联调的时候就是一场灾难。我这次的做法是在项目一开始就定好契约后端统一返回 snake_case 字段名前端 axios 函数里统一做字段映射转换。刚开始觉得麻烦但联调时省了非常多来回沟通的时间。前端 api 模块长这样// api/task.js export function getTaskList(params) { return request({ url: /tasks, method: get, params }).then(res { return res.data.map(item ({ id: item.id, title: item.title, assigneeId: item.assignee_id, assigneeName: item.assignee_name, status: item.status, progress: item.progress, planStart: item.plan_start, planEnd: item.plan_end, isOverdue: item.is_overdue })) }) }日期格式的坑也值得一提。后端 MySQL 返回的日期是字符串格式比如2024-12-25Element UI 的日期组件默认接受 Date 对象或格式化字符串如果直接绑定会出现回显异常。我的处理是前端统一用date-fns的format函数格式化成YYYY-MM-DD再绑定提交时也转成同格式字符串不让 Date 对象进出接口层。4.5 项目上线部署整个系统最后部署在一台 4 核 8G 的云服务器上操作系统 Ubuntu。部署方案是 Nginx 做反向代理前端构建后的静态文件由 Nginx 直接托管后端用 Gunicorn 启动 Flask 服务。Nginx 配置最关键的两个点一是前端路由的 history 模式需要配置try_files回退到 index.html否则刷新页面会 404二是所有/api/路径的请求要反向代理到后端的 8000 端口。server { listen 80; server_name your_domain; root /var/www/project-manager/dist; index index.html; location / { try_files $uri $uri/ /index.html; } 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; } }这个部署方案里最容易忽略的是try_files这一行。前端项目用了 Vue Router 的 history 模式路由 URL 是真实路径而不是带#的 hash 模式。用户访问/project/5时Nginx 需要在磁盘上找project/5这个文件找不到就会回退到 index.html由前端路由接管。这一行不加页面刷新就 404是我记忆中遇到最多的部署问题。5. 常见问题与排查技巧实录5.1 跨域问题的完整排查路径开发模式下前端跑在localhost:8080后端跑在localhost:5000端口不一样就会触发浏览器的同源策略拦截。表现是前端控制台报 Access-Control-Allow-Origin 相关的错请求发出去了但响应被浏览器拦截。我的解决方案分两步。第一步开发环境走 Vue CLI 的代理在vue.config.js里配置 devServer 的 proxy把/api前缀的请求代理到http://localhost:5000这样前端发请求的地址是同源的浏览器不会拦截。第二步生产环境由 Nginx 转发配置上面已经写了。还有一次生产环境也遇到了跨域问题排查了半天发现是后端的 CORS 中间件写得太宽泛把允许的域名写死了localhost:8080导致上线后通过域名访问时配置不匹配。后来我把后端 CORS 配置改成动态读取请求的 Origin 头并回写同时只信任配置在环境变量里的域名白名单问题解决。5.2 时间字段未更新与“进度保存成功但不刷新”有个现象值得单独说一下任务进度保存成功后看板上的进度条没有变化。排查后发现问题不在后端而在前端的缓存。Vue 的 computed 属性对后端返回的 progress 做了缓存任务对象没有变更视图就不会重新渲染。解决方法是更新后对任务列表重新拉取async function refreshTasks() { loading.value true const data await fetchTaskList(currentFilter.value) taskList.value data loading.value false }重新赋值整个数组而不是修改原有对象的属性Vue 的响应式系统能检测到引用变化强制重新渲染。这个小知识点在 Vue 2 里特别重要当然 Vue 3 用 Proxy 重写了响应式已经不需要这么粗暴了但重新拉取数据保证一致性仍然是我推荐的做法。5.3 多人同时更新一个任务导致的覆盖问题上线后遇到过一个真实事故两个成员几乎同时更新阅读同一项目下的不同任务发现改完以后另一个人的进度丢失了。查日志发现是更新接口的 SQL 语句只更新了单条记录但前端在保存后做了一次全量刷新刷新请求覆盖了页面里的部分任务状态。这个问题的根源是并发写和读没做好隔离。后端这块我用行级版本号解决任务表加了一个version字段每次更新时检查传入的 version 是否和数据库一致不一致就拒绝更新让前端重新拉取。同时前端优化了交互保存成功后只重新拉取当前任务详情不做整页刷新。5.4 常见问题速查表我整理了开发过程中遇到的高频问题做成一张速查表供参考。问题现象根本原因解决方案前端请求 404后端接口正常路由 history 模式未配置 try_filesNginx location / 加 try_files 回退 index.html请求发出但被浏览器拦截跨域配置缺失或白名单不匹配配置 CORS 白名单开发环境用代理登录成功后刷新页面又回到登录页token 存在内存 but 刷新丢失token 同时持久化到 localStorage上传日期字段回显为 1970-01-01日期字符串格式不匹配统一使用 YYYY-MM-DD 字符串格式任务状态改了但进度不变状态和进度是独立字段后端未联动后端统一在 progress 更新接口里处理状态流转npm run build 报内存溢出项目依赖过多构建内存不足NODE_OPTIONS--max-old-space-size40965.5 上线后的真实运营体会系统上线跑了两个多月几个当初没预料到的现象让我印象很深。第一员工对“自己名下的任务列表”接受度最高因为省了他们记挂该做啥的精力第二项目经理一开始觉得录入任务麻烦两周后真香了因为周报可以直接从系统导出不用自己整理第三延期率并没有立刻下降但是延期的发现时间从交付日提前到了截止前一周这就是管理工具带来的实际价值。这套系统后续如果要扩展我会优先加两个功能一是消息通知任务分配、逾期预警自动发通知二是操作日志的展示页面目前日志表一直在记录但没有做前端展示。如果再把报表这块补上比如成员效率统计、项目周期对比就能从管理工具进一步升级为决策支持工具。写在最后这个项目从需求确认到正式上线前后大概是六周时间其中编码四周期联调和部署两周。用 Python Vue 这套组合做企业内部管理系统我个人的体会是开发效率高、维护门槛低非常适合中小团队的工具类需求。如果你准备复刻这套方案我建议先花一天时间把需求文档写清楚特别是任务状态流转这种业务规则写清楚再动手能少走很多弯路。数据库设计上任务表务必带上version字段后续并发问题会让你感谢当初多写的这一行。前端方面axios 拦截器的统一封装一定要做不然后面每个接口都要重复写鉴权逻辑会烦到你怀疑人生。最后分享一个经验内部系统不需要花哨但一定要把“明天有谁要交什么、哪个项目快延期了”这些信息放在用户打开系统第一眼就能看到的位置。工具的价值不在功能数量而在它是否真的解决了团队每天的实际问题。
返回列表