ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于Django与Flask的员工管理系统开发实战:从后端到前端全流程

基于Django与Flask的员工管理系统开发实战:从后端到前端全流程 得先声明一下这篇不是什么高深的理论文章就是一套我已经落地跑过的“员工管理系统”完整实操记录。业务背景很简单公司人事还在用Excel管员工档案部门一多、离职入职一频繁表格就乱成一锅粥。于是我用Python的Django做核心后端用Flask补了一个轻量级的辅助服务前端交给Vue整个开发过程都在PyCharm里完成的。下面把设计和实现过程完整拆给你看该给的代码、配置、避坑记录一个都不会少。1. 先把需求揉碎员工管理系统到底在管什么很多新手拿到“员工管理”这个题目就一头扎进代码里结果做着做着就发现逻辑越搅越乱。我建议你先别碰键盘找个会议室把人事、行政、部门主管拉过来聊半个钟头把真实的业务流程画出来。这套系统的需求其实就是这么聊出来的。当时梳理出来的核心业务点有四个员工档案管理员工基本信息、联系方式、身份证号、入职日期、学历、紧急联系人等必须支持增删改查。部门结构管理公司有多个部门还会出现“一部三组”这种层级关系需要树形结构去维护。离职与异动记录员工调岗、转正、离职这些状态不能直接硬删数据要有状态流转和操作留痕。工资信息对接前端要看某个月各部门的工资汇总直接让后端算好再返回而不是把几条明细全部丢给前端去加。顺着需求往下走就是数据库表设计。我最终落地的核心表有5张表名主要字段说明departmentid, name, parent_id, manager_id部门表自关联实现树形employeeid, emp_no, name, gender, phone, id_card, department_id, position, hire_date, status员工档案主表status标记在职/离职/试用attendanceid, employee_id, work_date, status考勤记录表salaryid, employee_id, month, base_salary, bonus, deduction工资明细表month格式示例2025-06userid, username, password, employee_id, is_superuser系统登录账户与员工一对一关联这里有个小设计经验员工编号emp_no不要用自增ID直接暴露给前端。我加了一个YYYYMMDD加三位序列号的生成策略比如“20250615001”好处是人事部门光看编号就能判断入职批次而且后续做导入导出时不会和Excel里的数据冲突。数据库层面因为项目中有些报表要跨表联查比如“查每个部门在职人数”我后来增加了一张department_staff_count的视图。视图在Django里不用去改模型文件直接用原生SQL建在MySQL里面即可ORM查询时当普通模型用managed False映射就行。2. 技术选型不纠结Django和Flask是“主角配角”的关系看到标题里有“django flask”两个关键词很多人第一反应是问“到底用哪个”。我的回答是都用了但不是平起平坐地各写一半业务。主后端是DjangoFlask只在一个独立的小服务里承担活儿。说下这么设计的理由。员工管理系统本质上是一个“后台管理系统报表服务”的组合这类系统最忌讳的就是从零搭造轮子。Django自带Admin后台、ORM、用户认证、中间件、迁移机制这让我在两周内就把业务骨架搭完了尤其是Admin界面可以直接给人事部门做数据录入后期还能用django-simpleui或者django-jazzmin美化不用自己写一堆后台模板。Flask适合做嵌入式的轻量服务启动快、依赖少占用资源低。我就用Flask单独起了两个接口一是工资Excel导出二是员工批量导入时的数据清洗。这两个功能单独拆出来之后Django进程不会因为大文件上传或复杂Excel计算阻塞常规API请求。雇一整个项目里整体调用链路是这么串起来的Vue前端按下按钮请求打到DjangoDjango在收到导出请求时转发给Flask服务Flask内部用openpyxl拼好Excel文件、返回二进制流Flow再由Django流回前端。Django扮演了“API网关业务核心”Flask扮演了“文件处理工人”。这套组合还有一个好处Flask服务可以单独部署到另一台机器上如果后续需要处理更重的Excel计算比如十万行工资数据直接给Flask扩容就行不影响主业务。如果你只想用一个框架明确告诉你核心管理功能完全用Django就能搞定Flask只是锦上添花。Flask适合的场景是你已经有一套主业务系统不一定是Django但不想为了“导出一个Excel”就引入一整个重型框架。3. Django后端实操从建虚拟环境到API接口全流程3.1 PyCharm里从零初始化项目我开发时用的是PyCharm Professional但社区版也完全能跑这套项目没有哪个功能是专业版独占的。打开PyCharm后我是这样创建项目的新建项目选择Virtualenv环境Python解释器选系统里已装好的Python 3.10。在Terminal里依次执行命令安装依赖包pip install django djangorestframework django-cors-headers mysqlclient openpyxl这里专门提一下mysqlclient。Windows装它经常出幺蛾子报错多半是缺少VC编译环境。我现在都是直接下载对应Python版本的whl文件用pip离线安装五秒钟搞定比让pip现场编译省心一百倍。创建Django项目和两个应用django-admin startproject employee_system cd employee_system python manage.py startapp employee python manage.py startapp department创建应用这个动作正常情况下没人废话但很多初学者会犯一个错把十个业务模块塞进同一个app里。我建议按业务域拆分员工相关接口放employee应用部门相关放department应用以后做权限控制、代码维护都清晰。3.2 数据模型设计与迁移下面直接放出员工主表的模型代码这是整个系统最核心的一张表from django.db import models from department.models import Department class Employee(models.Model): STATUS_CHOICES ( (probation, 试用期), (active, 在职), (resigned, 离职), ) emp_no models.CharField(max_length20, uniqueTrue, verbose_name员工编号) name models.CharField(max_length50, verbose_name姓名) gender models.CharField(max_length10, choices((male, 男), (female, 女)), verbose_name性别) phone models.CharField(max_length20, blankTrue, verbose_name手机号) id_card models.CharField(max_length18, blankTrue, verbose_name身份证号) department models.ForeignKey(Department, on_deletemodels.PROTECT, verbose_name所属部门) position models.CharField(max_length50, blankTrue, verbose_name岗位) hire_date models.DateField(verbose_name入职日期) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultprobation, verbose_name状态) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) updated_at models.DateTimeField(auto_nowTrue, verbose_name更新时间) class Meta: db_table employee indexes [models.Index(fields[status]), models.Index(fields[department])] ordering [-hire_date] def __str__(self): return f{self.emp_no} {self.name}设置里我特意加了on_deletemodels.PROTECT意思是如果部门下面还有员工这个部门就不允许在界面上被删掉。表面上看是给操作添堵实际上保护了数据的完整性——否则某天管理员手一抖把部门删了底下几十个员工全变成“无部门”孤儿数据那才叫灾难。3.3 DRF视图集与路由员工管理接口我用的是viewsets.ModelViewSet因为它的逻辑和RESTful风格完美匹配增删改查都有现成实现只要在数据校验上做定制即可from rest_framework import viewsets, filters from rest_framework.pagination import PageNumberPagination from .models import Employee from .serializers import EmployeeSerializer class EmployeePagination(PageNumberPagination): page_size 15 page_size_query_param page_size max_page_size 100 class EmployeeViewSet(viewsets.ModelViewSet): queryset Employee.objects.select_related(department).all() serializer_class EmployeeSerializer pagination_class EmployeePagination filter_backends [filters.SearchFilter, filters.OrderingFilter] search_fields [name, emp_no, phone] ordering_fields [hire_date, created_at] def perform_destroy(self, instance): # 员工离职不等于删除记录改为软删除 instance.status resigned instance.save()请注意perform_destroy这个重写。常规做法是直接instance.delete()但在员工管理系统里“删除”这个动作是有业务含义的——离职员工的数据要留着财务以后对账还要翻历史记录。所以我把删除动作改成了状态置为离职。这也是做业务系统时一个非常重要的意识数据库记录能别硬删就别硬删加个状态字段比啥都管用。然后在urls.py里注册from rest_framework.routers import DefaultRouter from employee.views import EmployeeViewSet router DefaultRouter() router.register(remployees, EmployeeViewSet, basenameemployee) urlpatterns [ path(api/, include(router.urls)), ]这套配置做完即使不写一行前端代码Django自带的接口文档就已经能访问了/api/employees/返回JSON数据。我开发时习惯先用Postman把接口调通再去做Vue页面这样查错范围能缩短一半。3.4 Django Admin界面美化与数据兜底再聊一句Admin。系统刚上线那阵子前端Vue页面还没做完但人事那边已经急着录入数据了。我直接把模型注册进Admin再把Admin后台用django-jazzmin换了个皮肤一个简易后台就能先给业务部门用起来。注册代码很简单from django.contrib import admin from .models import Employee admin.register(Employee) class EmployeeAdmin(admin.ModelAdmin): list_display (emp_no, name, department, position, status, hire_date) list_filter (status, department) search_fields (name, emp_no) date_hierarchy hire_date生命周期里Admin后台不光是前期补数据的方式也是后期开发者自查数据库内容的工具箱——查一条脏数据、改一个错误状态直接进Admin改比连数据库敲SQL快得多。4. Flask侧翼服务用openpyxl实现工资Excel导出4.1 为什么单独拆一个Flask进程刚开始我的Excel导出功能直接写在Django里功能没两天就写完了但发现一个问题导出工资表时数据量大一点就会占住Django的worker进程浏览器等到超时其他API也卡住。Django同步框架下这种耗时任务会阻塞请求处理线程。所以我决定把导出任务单独拆出去用Flask开一个独立服务。有的朋友会说“你这场景交给Celery异步任务不就行了”。确实是方案之一但当时后端服务部署在只有2G内存的小ECS上Celery要额外跑一个worker进程、还要配Redis复杂度瞬间上去了。对于一个导出功能Ligrape解决方案——独立Flask服务——部署成本低、责任边界清晰之后把服务一停完全不影响主系统维护起来心理负担小。4.2 Flask代码实现from flask import Flask, request, send_file, jsonify from openpyxl import Workbook from openpyxl.styles import Font, PatternFill import io app Flask(__name__) app.route(/export/salary, methods[POST]) def export_salary(): data request.get_json() month data.get(month) rows data.get(rows, []) wb Workbook() ws wb.active ws.title f{month}工资表 headers [工号, 姓名, 部门, 基本工资, 奖金, 扣款, 实发工资] ws.append(headers) header_font Font(boldTrue) fill PatternFill(start_colorCCCCFF, end_colorCCCCFF, fill_typesolid) for col_idx, h in enumerate(headers, 1): cell ws.cell(row1, columncol_idx) cell.font header_font cell.fill fill total 0.0 for row in rows: actual round(float(row[base_salary]) float(row[bonus]) - float(row[deduction]), 2) ws.append([row[emp_no], row[name], row[department], row[base_salary], row[bonus], row[deduction], actual]) total actual ws.append([, , 合计, , , , round(total, 2)]) output io.BytesIO() wb.save(output) output.seek(0) filename fsalary_{month}.xlsx return send_file(output, as_attachmentTrue, download_namefilename, mimetypeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet) if __name__ __main__: app.run(host0.0.0.0, port5001)关键点在io.BytesIO。如果你把Excel先存到服务器临时文件再读回来会产生一堆需要清理的垃圾文件用BytesIO把文件流直接放在内存里返回请求结束内存自动释放干净利落。然后Django端只需要用一个requests.post把数据转给Flask再把响应流原样返回给前端import requests from django.http import HttpResponse def forward_salary_export(request): month request.GET.get(month) salary_rows SalaryService.get_rows_by_month(month) resp requests.post(http://127.0.0.1:5001/export/salary, json{ month: month, rows: salary_rows, }, timeout30) file_name fsalary_{month}.xlsx response HttpResponse( resp.content, content_typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet ) response[Content-Disposition] fattachment; filename{file_name} return response平时用的时候Django进程和Flask进程在同一台机器上时地址就是127.0.0.1:5001。如果哪天Flask服务迁到独立机器只要把127.0.0.1改成对应内网IP理论上Django代码一行不用动服务就解耦了。5. Vue前端与接口联调从页面骨架到Token鉴权5.1 Vue项目初始化与依赖前端我直接用Vue CLI先搭vue create employee-web cd employee-web npm install element-plus axios vue-router4 npm install -D sass sass-loader装依赖的时候常有人卡在node-sass编译上我的经验是尽量用sassdart-sass它不需要本地编译兼容性比node-sass好太多。这个坑踩过的人都知道版本不对直接报错一大片。组件库选了Element Plus后台管理界面那一套表格、表单、弹窗、消息提示开箱即用开发效率拉满。5.2 路由与页面骨架员工管理主页面我用el-card加el-table组合左侧放部门树右侧放员工表格。部门树要展开设计上用了el-tree数据接口是后端返回的列表前端JS手动组装成树形function buildTree(list, parentId null) { const tree [] list.forEach(item { if (item.parent_id parentId) { const children buildTree(list, item.id) if (children.length) { item.children children } tree.push(item) } }) return tree }这块逻辑堪称面试精典——树形数据组装。对于部门层级不多、最多三四层的企业场景来说前端组装足够后端没必要专门递归一次。员工表格列配置关键代码如下el-table :dataemployeeList v-loadingloading border stripe el-table-column propemp_no label员工编号 width130 / el-table-column propname label姓名 width100 / el-table-column propdepartment.name label部门 width140 / el-table-column propposition label岗位 width120 / el-table-column propstatus label状态 width100 template #defaultscope el-tag :typestatusType(scope.row.status) {{ statusText(scope.row.status) }} /el-tag /template /el-table-column el-table-column prophire_date label入职日期 width120 / el-table-column label操作 min-width200 fixedright template #defaultscope el-button sizesmall clickopenEdit(scope.row)编辑/el-button el-button sizesmall typedanger clickresign(scope.row)离职/el-button /template /el-table-column /el-table注意这里的propdepartment.name它依赖后端序列化器把关联部门信息嵌套返回不然表格里只能显示一个部门ID毫无意义。所以序列化器里要加上department DepartmentSerializer(read_onlyTrue)。5.3 登录授权与Axios请求封装系统的登录逻辑是用户在登录页输入用户名密码Vue请求/api/auth/login/Django用DRF自带的TokenAuthentication签发tokenVue把token存在localStorage里。之后每个请求都在拦截器里把token塞进请求头axios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Token ${token} } return config }) axios.interceptors.response.use( response response, error { if (error.response error.response.status 401) { router.push(/login) localStorage.removeItem(token) } return Promise.reject(error) } )判断是否登录成功前端只认HTTP状态码200和返回体里的token字段如果401直接踢回登录页。这算是后台系统最基础也最必要的防御手段没有token校验的系统等于把接口裸奔在公网上任何人拿Postman都能删数据。5.4 Vue构建与本地联调的CORS问题联调阶段最容易出的问题就是跨域。开发环境我用Vite的proxy代理生产环境用Nginx反代。配置在vue.config.jsmodule.exports { devServer: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } }这比在Django里配django-cors-headers省心因为生产环境走同域Nginx把/api转给Django开发环境用代理两边都不用担心cookie和CORS的坑。不过需要注意Django的ALLOWED_HOSTS里如果配置的是localhost而Vue代理转发请求头里的Host是localhost:8000一般没事但如果你后面前端部署域名和Django域名不一样必须把域名加进ALLOWED_HOSTS否则Django直接拒绝访问。6. PyCharm配置与全流程踩坑实录6.1 PyCharm里的环境配置下面这些操作是我在另一台全新电脑上重装环境时走了一遍的流程照着做能少踩不少坑。用PyCharm打开项目之后第一件事是配置解释器。如果直接用系统Python很容易装一堆依赖污染全局环境。我现在一律用PyCharm自带的Virtualenv创建方式Settings - Project - Python Interpreter - Add Interpreter - Virtualenv Environment - New。创建好虚拟环境后把Django项目的运行配置加一下Run - Edit Configurations - 新增“Django Server”配置在Parameters里写runserver 0.0.0.0:8000这样局域网内手机和同事电脑都能通过IP访问。很多人的PyCharm只写runserver默认只监听127.0.0.1手机永远没法调试。6.2 高频踩坑点与解决方法列几个这套系统中真正绊倒过我的问题按频率排序问题表现解决方法mysqlclient安装失败pip install报错error: Microsoft C Build Tools下载whl离线安装CORS跨域浏览器请求报blocked by CORSdevServer代理或Nginx同域反代时区数据混乱入职日期总差一天Django设置USE_TZ False时间字段统一用DATEVue打包后路由刷新404直接访问子路径报404Nginx配置try_files $uri $uri/ /index.html;FileField上传后图片不显示图片路径404settings.py配置MEDIA_URL和MEDIA_ROOTNginx加静态路由其中“Vue打包后路由刷新404”这个问题第一次部署时让我排查了整整一个下午。原因是Vue Router用的history模式刷新/employee这个地址时Nginx不知道去哪找对应的页面文件于是直接404。解决办法是让Nginx把所有非静态资源的请求都回退到index.htmllocation / { root /usr/share/nginx/html/employee-web; index index.html; 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; }请记住这个经典配置。很多后台管理系统上线后“整个页面都能打开一刷新就白屏”的诡异问题基本都是这个导致的。还有一种情况是无意中让Vue的base路径配置错误如果你把前端部署在域名子路径下比如/admin/vue.config.js里要同步设置publicPath: /admin/。6.3 员工导入时的数据清洗工具员工批量导入功能我放在了Flask服务里因为数据清洗逻辑作为独立工具更清晰。平时上传一个几百行的Excel里面姓名前后带着空格、手机号格式不统一、部门名称和系统里对不上。Flask服务这端先把Excel逐行读出来做标准化后再调Django的导入API。具体实现思路是这样的Excel传入后先用openpyxl读取每行数据姓名用strip()去掉空格手机号用正则1[3-9]\d{9}校验部门名称去系统部门表里做精确匹配匹配不到的部门先给标记为“待确认部门”再返回一份清洗报告。整个过程不修改真实数据报告确认无误后才真正导入数据库。这段逻辑如果写在Django主服务里也能实现但当时考虑到它可能要处理大文件和复杂循环不想影响主API响应。放在Flask里主服务挂了Flask还在跑数据清洗还能继续用两个进程互不干扰维护起来边界清楚。7. 一次真实上线事故复盘从卡顿到优化项目上线首周人事反馈“导出工资表特别慢”刚开始没当回事想着几百行数据能慢到哪去。后来自己亲自测了一次发现导出一次需要将近6秒确实到了让人崩溃的程度。定位过程大概分了四步。第一步先确认慢在前端还是后端。打开浏览器开发者工具发现等待接口返回的时间占了大头基本可以排除前端渲染慢。第二步看Django日志发现导出工资时Django端所有接口的响应都变慢了说明是Django进程被导出任务占住。第三步把数据量放大到全公司500人试了一下Django端光是构造JSON再转交Flask就要处理差不多2秒。第四步排查是慢在数据库还是慢在网络给SQL加上EXPLAIN一看问题出在salary表按month字段范围查询时没用上索引数据库全表扫了500条数据居然扫了很久。优化办法很简单给salary表加上组合索引month, employee_id。加完之后导出耗时从6秒降到了1秒以内。说实话这个优化根本不复杂但它提醒了我当一个系统开始卡先别怀疑框架先查数据库有没有走索引查接口有没有N1查询这两个地方通常是绝大多数性能问题的根源。8. 最后分享两个提升幸福感的小配置折腾完这套系统之后有几个小而美的配置我强烈建议你顺手加上能省后面很多事。一个是Git版本管理。刚建项目时就把.gitignore写好把venv/、node_modules/、__pycache__/、*.pyc、db.sqlite3全排除掉不然每次提交代码几百MB的依赖包一起传上去仓库直接废掉。另一个是Django项目里的环境变量管理。不要直接把数据库密码、SECRET_KEY硬编码在settings.py里用python-dotenv读取.env文件。这样项目传到Git或者发给同事时.env不进版本库数据库密码不会泄露换环境也只要改一个文件。配合PyCharm的EnvFile插件本地调试时自动加载.env里的变量特别顺手。这套员工管理系统的技术栈和代码算不上什么黑科技但它是那种“很落地、能治病”的项目。你拿它当毕设、当公司内部小工具、当练手项目都合适核心的Django建模、DRF接口、Vue联调、Flask旁路服务整条链路走一遍之后你会发现“前后端分离”这几个字不再是个概念而是你脑子里清清楚楚的一张地图。
返回列表