ARTICLE DETAIL

资讯详情

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

Django+Vue人事管理系统开发实战:从环境配置到ORM删除与接口联调

Django+Vue人事管理系统开发实战:从环境配置到ORM删除与接口联调 做企业人事管理系统我前后折腾过两个版本——一开始图省事用 Flask 快速搭了个小样后来发现模块多了以后还是 Django 更顺手前端则从服务端模板渲染那套切成了 Vue 前后端分离。今天把这套东西彻底拆开从 PyCharm 开发环境怎么配到 Django ORM 里“查询-删除对象”容易踩的坑再到 Vue 路由、插槽以及和 Django 接口联调的细节一次讲清楚。这个项目适合 Python 刚入门、正在做课设或毕设的人事管理系统或者公司内部需要一套轻量级 HR 信息管理后台的开发者参考。我尽量按“能用、能跑、能改”的标准来写所有代码片段都是实际验证过的你照着搭一套基础人事管理系统半天时间基本够用。1. 项目整体设计与技术选型1.1 为什么是 Python Vue而不是其他方案人事管理系统要处理的核心数据其实就几类员工档案、部门信息、考勤记录、工资条偶尔加个公告或请假审批。这类系统的特点是数据模型稳定、表单交互多、列表和筛选逻辑重。用 Python 写后端最大的优势是开发速度快Django 和 Flask 都有非常成熟的生态前端用 Vue 则是因为它的组件化和响应式机制特别适合表单密集型场景员工信息增删改查这种页面用 Vue 写要比操作 DOM 那套远古方案省太多事。如果你问我为什么不选 Java Spring Boot React我只能说人力和时间成本不一样。Python Vue 这套组合一个人就能从数据库设计写到前端页面调试链路的长度也短。而且 Django 自带 Admin 后台这一点非常作弊——哪怕你前端还没开始写Django Admin 就已经给你一个能用的员工管理界面了这在项目中期验收时相当救命。1.2 Django 和 Flask 到底怎么选这是被问得最多的问题。先说结论如果你要做的是员工、部门、考勤、薪资这类多模块系统直接选 Django如果只是做一个简单的部门通讯录或者一个几百行的演示 DemoFlask 更轻。Django 的优势是“全家桶”ORM、Admin 后台、认证系统、表单处理、中间件机制全给你配好了数据模型建好以后增删改查的接口逻辑写起来非常快。它还自带一个开发服务器改了代码自动重载调试效率很高。Flask 的优势是“小而自由”你只用 Flask-SQLAlchemy 做 ORM用蓝图模块化管理路由整个项目结构完全由自己掌控。但代价是很多东西需要自己拼比如用户认证要装 Flask-Login表单校验要装 WTForms接口序列化要自己写或装 Flask-RESTful。我的建议是如果你还在犹豫或者项目周期短、功能模块多于五个无脑 Django。我第二个版本整体从 Flask 迁到 Django 后最大的感受就是删代码比写代码更爽因为好多轮子不用自己造了。1.3 功能模块与数据库设计人事管理系统的基础模块我整理成五张核心表表名核心字段说明departmentname, manager_id, create_time部门表manager_id 指向员工employeename, gender, phone, email, department_id, position, salary, hire_date, status员工表外键关联部门attendanceemployee_id, date, status考勤表status 区分正常/迟到/缺勤salaryemployee_id, month, base, bonus, deducted工资表按月记录userusername, password_hash, role登录账号role 区分管理员/普通员工这里面最需要注意的就是 employee 和 department 的外键关系以及 salary 按“员工 月份”作为唯一索引。我在第一个 Flask 版本里就吃过亏——工资表没有加唯一约束导致同一个人同一个月的工资条在批量导入时插入了两条前端列表直接出现了重复行。所以建表时一定要想清楚哪些字段组合是唯一的。2. 开发环境搭建PyCharm、Python 与 Vue2.1 PyCharm 安装与基本配置PyCharm 是 JetBrains 家的 IDE做 Python 开发体验最好的是它的调试功能和代码补全。直接去官网下载 Community 版就够用没必要一开始就上专业版。专业版多出的 Django 模板和数据库工具确实香但社区版装个插件也能补上大半。装好以后第一件事是配置解释器打开 Settings - Project - Python Interpreter选择已有的 Python 环境或者新建一个虚拟环境。我强烈建议每个项目都用独立虚拟环境千万别把 Django、Flask 装到全局 Python 里不然多个项目依赖版本冲突能把人逼疯。虚拟环境创建好以后PyCharm 会自动识别 venv 目录下的 python 解释器你只需要在终端里确认 python 命令指向的是这个虚拟环境。PyCharm 让我觉得离不开的一个功能是调试断点。比如 Django ORM 执行删除操作时你可以直接在调用 .delete() 的那一行打上断点然后 Step Into 进去看 Django 到底执行了几条 SQL。这个能力在排查数据删除级联问题时比 print 大法好用一百倍。2.2 Python 虚拟环境与依赖安装创建虚拟环境的方式有两种任选其一# 方式一直接用 venv python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 方式二用 conda如果你装了 Anaconda conda create -n hrms python3.11 conda activate hrms虚拟环境激活后安装依赖pip install django djangorestframework django-cors-headers pip install flask flask-sqlalchemy flask-cors这里有个细节容易坑到新手Django 从 3.2 以后要求 Python 版本不低于 3.8到了 Django 5.x 则需要 Python 3.10 以上。如果你还在用 Python 3.7 跑老项目升级 Django 前先看官方版本兼容表。我建议直接用 Python 3.11 或 3.12新的总比老的好而且 Python 3.12 的性能提升在 ORM 查询密集的场景里能感受到。2.3 Vue 开发环境搭建Vue 前端的环境配起来比 Python 略烦一点因为要装 Node.js 和 npm。先去官网下载 Node.js LTS 版本安装完后在终端里验证node -v npm -v然后全局安装 Vue CLI老牌但稳定或者直接用 Vite 创建新项目。我的建议是新手用 Vue CLI老手用 Vite。Vue CLI 生成的工程结构非常规整webpack 配置都封装好了你不需要理解太多构建原理就能跑起来Vite 启动速度确实快一个量级但它默认配置更精简有些东西要自己装。# 安装 Vue CLI npm install -g vue/cli # 创建项目 vue create hrms-web # 选择 Vue 3 预设重点 # 然后选择 Router 和 Axios创建完成后进入项目目录运行开发服务器cd hrms-web npm run serve默认跑在 8080 端口。后端的 Django 开发服务器默认跑在 8000 端口前后端联调时就要涉及跨域问题这个我在后面“常见问题”那一节详细讲。3. 后端核心开发Django 实战3.1 创建 Django 项目与 AppDjango 的项目结构我建议这样划分django-admin startproject hrms_backend cd hrms_backend python manage.py startapp employee python manage.py startapp department python manage.py startapp attendance python manage.py startapp salary不要把所有模型都塞进一个 app 里。很多新手图省事在默认的 app 里建了十张表最后代码全堆在 models.py 一个文件里——短期看着方便一旦项目超过三张表维护成本就会爆炸。一个业务模块对应一个 app这是 Django 的基本工作方式。创建完 app 后记得在 settings.py 的 INSTALLED_APPS 列表里注册INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, rest_framework, corsheaders, employee, department, attendance, salary, ]另外加上中间件和数据库配置。如果你用的是 MySQL就要装 PyMySQL 并在__init__.py里加一句pymysql.install_as_MySQLdb()如果图省事直接用 SQLite连配置都不用改默认就行。我建议开发阶段先用 SQLite等部署再切 MySQL省掉一大半调数据库连接的折腾。3.2 数据模型定义以 employee 为例模型长这样from django.db import models class Employee(models.Model): name models.CharField(max_length50, verbose_name姓名) gender models.CharField(max_length10, choices[(M, 男), (F, 女)], verbose_name性别) phone models.CharField(max_length20, blankTrue, verbose_name电话) email models.EmailField(blankTrue, verbose_name邮箱) department models.ForeignKey( department.Department, on_deletemodels.SET_NULL, nullTrue, blankTrue, verbose_name所属部门 ) position models.CharField(max_length50, blankTrue, verbose_name职位) salary models.DecimalField(max_digits10, decimal_places2, default0, verbose_name薪资) hire_date models.DateField(auto_now_addTrue, verbose_name入职日期) status models.CharField( max_length10, choices[(active, 在职), (leave, 离职)], defaultactive, verbose_name状态 ) class Meta: db_table employee verbose_name 员工 ordering [-hire_date] def __str__(self): return f{self.name} - {self.position}这里外键的on_delete参数是个经典考点。员工所属部门如果被删除员工的 department_id 怎么处理用SET_NULL表示部门删了员工还在但是部门字段置空用CASCADE表示部门删了整个部门下的员工全删——这个要看业务需求。人事系统里部门解散通常不删员工所以SET_NULL比CASCADE更安全。建好模型后依次执行python manage.py makemigrations python manage.py migrate3.3 ORM 查询与删除对象的关键细节搜索热词里有一条“django执行查询-删除对象”这确实是新手容易出问题的地方。Django 删除对象的方式有几种语义完全不同单对象删除emp Employee.objects.get(id1) emp.delete()get返回单个模型实例delete()会执行 DELETE SQL。这里有个坑如果员工被其他表外键引用了默认会级联删除。比如我前面设计的 attendance 表如果外键指向 employee 且 on_delete 是 CASCADE你删掉一个员工他的所有考勤记录会一并消失。这在有些业务场景里是灾难。QuerySet 批量删除Employee.objects.filter(department_id3).delete()QuerySet.delete()是批量删除Django 会先 SELECT 出所有符合条件的主键再一条条 DELETE。注意批量删除不会调用模型自定义的 delete() 方法也不会触发信号signal如果你在模型里重写了 delete 去做日志记录、级联操作用 queryset 批量删的时候这套逻辑是失效的。很多项目里“离职批量清数据”后就发现日志没记上原因就在这。安全删除的实践我实际项目里强烈建议用“软删除”——给 Employee 表加一个is_active字段删除时只更新这个字段emp Employee.objects.get(id1) emp.is_active False emp.save() # 查询时默认过滤已删除数据 Employee.objects.filter(is_activeTrue)软删除的好处是数据还在随时可以恢复考勤和工资的历史记录也不会断。代价是每个查询都要记得过滤 is_active容易忘。我的折中方案是数据量小几千条以内用软删除数据量巨大或归档需求明确的用硬删除 外键保护。3.4 接口开发与序列化我用 Django REST FrameworkDRF写接口比手写 JsonResponse 省事太多。安装后在 settings.py 里注册rest_framework然后创建序列化器from rest_framework import serializers from .models import Employee class EmployeeSerializer(serializers.ModelSerializer): department_name serializers.CharField(sourcedepartment.name, read_onlyTrue) class Meta: model Employee fields [id, name, gender, phone, email, department, department_name, position, salary, hire_date, status]再写视图用 DRF 的 ModelViewSet 直接拿到一整套 CRUD 接口from rest_framework.viewsets import ModelViewSet from .models import Employee from .serializers import EmployeeSerializer from rest_framework.permissions import IsAuthenticated class EmployeeViewSet(ModelViewSet): queryset Employee.objects.filter(is_activeTrue) serializer_class EmployeeSerializer permission_classes [IsAuthenticated] def perform_destroy(self, instance): # 改写删除逻辑改成软删除 instance.is_active False instance.save()perform_destroy这个钩子非常关键——你在这里改写删除行为前端调 DELETE 接口时后端做的是软删除对外接口语义完全不变但数据保住了。最后在 urls.py 里注册路由from rest_framework.routers import DefaultRouter from employee.views import EmployeeViewSet router DefaultRouter() router.register(api/employees, EmployeeViewSet, basenameemployee) urlpatterns [ path(admin/, admin.site.urls), ] router.urls注册完以后api/employees/自动支持 GET 列表、POST 新增、GET 详情、PUT 修改、DELETE 删除DRF 还自带一个可视化调试页面浏览器打开就能看到 JSON 数据。这个开发体验比 Flask 手写路由舒服太多。4. Vue 前端实现与接口联调4.1 Vue 项目结构与路由配置前端项目的目录我用 Vue CLI 默认的 src 结构按模块划分 views 和 componentssrc/ ├── api/ # 封装 axios 请求 │ └── employee.js ├── components/ # 通用组件 │ ├── EmployeeTable.vue │ └── EmployeeForm.vue ├── views/ # 页面级组件 │ ├── Dashboard.vue │ ├── EmployeeList.vue │ ├── DepartmentList.vue │ └── Login.vue ├── router/ │ └── index.js └── App.vue路由配置是 Vue 项目里最基础的环节import { createRouter, createWebHistory } from vue-router import EmployeeList from ../views/EmployeeList.vue const routes [ { path: /, redirect: /employees }, { path: /login, name: Login, component: () import(../views/Login.vue) }, { path: /employees, name: EmployeeList, component: EmployeeList, meta: { requiresAuth: true } }, { path: /departments, name: DepartmentList, component: () import(../views/DepartmentList.vue) } ] const router createRouter({ history: createWebHistory(), routes }) // 登录守卫 router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } }) export default router路由懒加载用() import()这种写法是标配一个模块一个 chunk首次加载速度能快不少。我在项目初期把所有页面都直接 import 进来打包后一个 JS 文件好几百 KB首屏白屏了很久改成懒加载后明显改善。4.2 员工列表与表单组件的核心逻辑员工列表页面是整个系统的门面我拆成三个组件搜索栏父组件里、表格EmployeeTable、编辑弹窗EmployeeForm。组件间通过 props 和 emit 通信。列表页核心代码template div el-input v-modelkeyword placeholder搜索姓名/职位 inputfetchList / EmployeeTable :employeesemployees editopenEdit deleteonDelete / EmployeeForm v-ifshowForm :employeecurrentEmployee saveonSave closeshowFormfalse / /div /template script setup import { ref, onMounted } from vue import { getEmployeeList, deleteEmployee } from ../api/employee import EmployeeTable from ../components/EmployeeTable.vue import EmployeeForm from ../components/EmployeeForm.vue const employees ref([]) const keyword ref() const showForm ref(false) const currentEmployee ref(null) const fetchList async () { const res await getEmployeeList({ keyword: keyword.value }) employees.value res.data.results } const openEdit (row) { currentEmployee.value row showForm.value true } const onDelete async (row) { await deleteEmployee(row.id) fetchList() } const onSave () { showForm.value false fetchList() } onMounted(fetchList) /script这里script setup是 Vue 3 的组合式 API 写法相比 Options API状态和处理函数的关系更清晰。Element Plus 作为 UI 组件库我强烈推荐——表格、表单、弹窗、分页都是现成的人事管理这种管理系统用 Element Plus 是最省力的方案不用自己瞎写样式。4.3 Axios 接口封装与拦截器前端和 Django 后端交互我用 axios 封装了一套 api 模块。先设置统一入口// src/api/request.js import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: http://127.0.0.1:8000/api, timeout: 10000 }) // 请求拦截器带上 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Token ${token} } return config }) // 响应拦截器统一处理错误 request.interceptors.response.use( response response.data, error { if (error.response?.status 401) { localStorage.removeItem(token) router.push(/login) ElMessage.error(登录已过期请重新登录) } else { ElMessage.error(error.response?.data?.detail || 请求失败) } return Promise.reject(error) } ) export default request然后按模块导出具体接口// src/api/employee.js import request from ./request export const getEmployeeList (params) request.get(/employees/, { params }) export const createEmployee (data) request.post(/employees/, data) export const updateEmployee (id, data) request.put(/employees/${id}/, data) export const deleteEmployee (id) request.delete(/employees/${id}/)拦截器的好处是错误处理统一收敛接口只负责数据获取和返回不用每个页面都写一遍 try-catch。4.4 Vue 插槽的高级用法热搜词里单独提到了“vue插槽”我觉得值得展开一下。Vue 的插槽slot本质上是组件的占位符允许你在子组件中预留一块区域由父组件动态填充内容。人事系统里最典型的场景是表格的操作列!-- EmployeeTable.vue -- template el-table :dataemployees el-table-column propname label姓名 / el-table-column propdepartment_name label部门 / el-table-column propposition label职位 / el-table-column label操作 !-- 使用插槽让父组件决定操作按钮 -- template #default{ row } slot nameactions :rowrow el-button click$emit(edit, row)编辑/el-button /slot /template /el-table-column /el-table /template父组件可以覆盖默认操作比如在离职员工的列表里加一个“恢复”按钮EmployeeTable :employeesemployees template #actions{ row } el-button typeprimary clickopenEdit(row)编辑/el-button el-button typedanger clickonDelete(row)删除/el-button el-button v-ifrow.status leave clickrestore(row)恢复/el-button /template /EmployeeTable这种“子组件定框架父组件定内容”的模式让表格组件可以被多个页面复用又不会把页面差异都塞进子组件。插槽是 Vue 组件设计里性价比最高的一项能力人事管理这种有很多重复表格的系统学会用插槽能让代码量直接减一半。5. Flask 备选方案与部署实践5.1 Flask 版本的快速实现思路如果你还是想用 Flask 做我给你一个最小可运行的结构免得走我第一版的弯路hrms_flask/ ├── app.py # 应用入口 ├── models.py # 数据模型 ├── views.py # 路由视图 ├── config.py # 配置 └── requirements.txtmodels.py 用 Flask-SQLAlchemy 定义 Employee 模型from flask_sqlalchemy import SQLAlchemy from datetime import datetime db SQLAlchemy() class Employee(db.Model): __tablename__ employee id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(50), nullableFalse) phone db.Column(db.String(20)) position db.Column(db.String(50)) salary db.Column(db.Numeric(10, 2), default0) hire_date db.Column(db.DateTime, defaultdatetime.now) status db.Column(db.String(10), defaultactive) def to_dict(self): return { id: self.id, name: self.name, phone: self.phone, position: self.position, salary: str(self.salary), hire_date: self.hire_date.strftime(%Y-%m-%d), status: self.status }视图层用蓝图把模块拆分员工接口长这样from flask import Blueprint, request, jsonify from models import Employee, db employee_bp Blueprint(employee, __name__, url_prefix/api/employees) employee_bp.route(/, methods[GET]) def list_employees(): keyword request.args.get(keyword, ) query Employee.query if keyword: query query.filter(Employee.name.contains(keyword)) employees query.all() return jsonify([e.to_dict() for e in employees]) employee_bp.route(/int:emp_id, methods[DELETE]) def delete_employee(emp_id): employee Employee.query.get_or_404(emp_id) db.session.delete(employee) db.session.commit() return jsonify({message: 删除成功})Flask 版本的删除就是典型硬删除——没有 Django 那种 queryset 和级联的概念一切都要自己控制。如果你用 Flask删除前务必手动检查外键引用不然删掉员工后考勤表里留下悬空的 employee_id前端联查时各种 NULL 错误。5.2 部署到服务器Gunicorn Nginx不管是 Django 还是 Flask上线部署的核心思路一致用 Gunicorn 挂 Python 应用Nginx 转发请求Vue 打包成静态文件交给 Nginx 托管。先收集后端的静态文件和依赖# Django python manage.py collectstatic pip freeze requirements.txt # 安装 gunicorn pip install gunicorn启动命令我习惯写成gunicorn hrms_backend.wsgi:application -b 0.0.0.0:8000 -w 4 --timeout 120-w 4表示 4 个工作进程一般每个 CPU 核配 2 个 worker--timeout 120是防止某些慢查询把 worker 卡死。Vue 前端打包npm run build打包后 dist 目录是一堆静态文件让 Nginx 把它们作为根目录同时把/api/开头的请求反向代理到 Gunicornserver { listen 80; server_name your_domain.com; root /var/www/hrms/dist; index 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; } location / { try_files $uri $uri/ /index.html; } }注意location /最后那行try_files ... /index.html——Vue 是单页应用前端路由的地址刷新时后端没有对应的文件必须把请求全兜到 index.html 交给 Vue Router 处理否则一刷新页面就 404。这个坑我第二次部署时踩了个结实。5.3 Flask 与 FastAPI 的比较顺便说一句现在后端界还有一伙人推荐 FastAPI我评估过它用来做人事管理——结论是不推荐。FastAPI 的优势在异步性能和自动生成 OpenAPI 文档但人事管理这种系统根本没有高并发压力它的异步能力发挥不出来而 FastAPI 的 ORM 生态比 Django 弱不少你还得自己写 Pydantic 模型、自己处理数据库会话开发效率反而不如 Django 全家桶。选型不是选最潮的是选最顺手的。如果你只是做内部工具或毕设Django 就是最短路径。6. 常见问题与避坑速查表6.1 环境安装PyCharm 里跑不起来这是新手最高频的问题我遇到的典型场景如下现象原因解决方案PyCharm 里 import django 报错解释器没选虚拟环境Settings - Project - Python Interpreter 切换终端能跑 manage.pyPyCharm 不能运行配置里没选正确的解释器Run - Edit Configurations 改 Python Interpreternpm run serve 报 node 版本太低Vue CLI 5 要求 Node 12升级 Node LTS 版本Django 迁移报No module named pymysql用 MySQL 但没装驱动pip install pymysql 并在配置里设置驱动另外 PyCharm 社区版默认不带模板引擎支持Django 模板的补全不那么舒服可以装 Django 插件够用就行。6.2 前后端联调跨域问题Django 后端跑 8000 端口Vue 开发服务器跑 8080 端口浏览器默认会拦截跨域请求。解决方式用 Django-cors-headerspip install django-cors-headerssettings.py 里加上INSTALLED_APPS [ corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, django.middleware.common.CommonMiddleware, ] CORS_ALLOW_ALL_ORIGINS True开发阶段直接CORS_ALLOW_ALL_ORIGINS True图省事但部署上线时一定要收敛成具体域名列表不然谁都能从任意网站往你后端发请求。6.3 删除对象的三大坑我观察到的删除相关报错和事故整理成速查删部门导致员工全没了——外键 on_delete 用了 CASCADE。检查模型定义改成SET_NULL同时让 department 字段允许为空。批量删除不触发软删除——Employee.objects.filter(...).delete()不走模型的delete()方法。要么把批量删除改成逐条调用要么在数据库层面加触发器。删员工后考勤记录变孤儿数据——硬删除前没检查关联表。建议先用软删除或者在外键上设置PROTECT让删除操作直接抛异常提醒你先处理关联数据。6.4 前端常见问题现象原因解决方案页面显示空白控制台报错路由模式用了 history 但服务器没配置改用 hash 模式或加try_files配置接口报 401 但登录接口是通的axios 没带 token检查请求拦截器确认 localStorage 里有 token表格数据更新后列表不刷新删除/新增后没重新拉取数据操作成功后调用fetchList()表单提交后日期字段丢失日期格式没转成 ISO 格式用 dayjs 格式化后再提交6.5 调试建议Django Debug Toolbar 是神器我最后推荐一个开发神器——Django Debug Toolbar。安装后每页请求都会在浏览器里显示 SQL 查询列表你能直观看到列表页触发了几条 SQL、有没有 N1 查询。这个问题在人事系统里非常典型员工列表页如果没做好select_related每展示一个员工都要多查一次部门表列表几十人就动不动几十条查询。加一行优化queryset Employee.objects.select_related(department).filter(is_activeTrue)查询次数直接从 N1 降到 1列表接口快得肉眼可见。调试工具的意义不是等出问题再去排查而是让你实时看到当前代码到底做了什么。做项目的过程中我最大的体会是人事管理系统虽然看着“只是增删改查”但真正决定开发体验的往往是细节——Django 的 ORM 删除策略、Vue 的组件复用方式、跨域配置、部署时单页路由的处理。把这些细节理顺剩下的就是业务表的堆叠而已。后端框架选 Django 还是 Flask我的建议已经说得很明确了。如果你只是做一期课设或内部工具直接照着我这套 Django Vue 的组合搭一遍数据模型跑通以后再往上面加考勤、工资、权限都只是加表加接口的重复劳动。最后记住一件事先把删除功能做好再考虑业务功能——人事数据删错了比加错了麻烦十倍。
返回列表