用Python+MySQL+Flask写一个简易的员工数据管理系统!

一、项目背景与需求分析

在日常企业管理场景中,HR 或管理人员需要快速地对员工信息进行增删改查、搜索筛选等操作。为了提升效率并降低人工维护成本,我们基于 Flask + SQLAlchemy 构建了后端 API,配合 Bootstrap + 原生 JavaScript 实现一个简洁易用的“员工信息管理系统”。

主要需求包括:

  1. 员工信息维护:新增、更新、删除员工记录;

  2. 列表展示与搜索:分页/前端过滤展示所有员工数据;

  3. 表单校验:前端强制字段(姓名、职位、邮箱、电话)必填,且电话必须 11 位数字;

  4. 界面美观:响应式布局、交互提示、表格与按钮样式优化;

  5. 后端健壮性:字段约束、唯一索引、错误处理与分页支持。


二、技术选型

层级技术栈说明
前端HTML5 + Bootstrap 5 + 原生 JS快速布局、响应式、无需额外框架
后端Flask 2.x + Flask-SQLAlchemy轻量级微框架 + ORM,提高开发效率
数据库MySQL(InnoDB) + SQLAlchemy ORM事务安全、自增主键、UTF8MB4 支持
开发/部署Python 3.8+ + pipenv/venv + WSGI Server便于虚拟环境管理,建议生产环境使用 Gunicorn/Nginx

本人配置的项目环境如下:


三、核心功能详解

3.1 前端页面结构

<div class="container py-4">
  <h1 class="mb-4">员工信息管理系统</h1>
  <!-- 搜索框 -->
  <div class="mb-3">
    <input id="search" class="form-control search-input"
           placeholder="搜索员工(姓名/职位/邮箱/电话)"
           oninput="filterEmployees()">
  </div>
  <!-- 员工表单 -->
  <form id="employeeForm" class="row g-2 mb-4 needs-validation" novalidate>
    <!-- ... 姓名、职位、邮箱、电话输入框,均带 required 与 invalid-feedback 提示 ... -->
  </form>
  <!-- 员工列表 -->
  <table class="table table-striped">
    <thead>
      <tr><th>ID</th><th>姓名</th><th>职位</th><th>邮箱</th><th>电话</th><th>操作</th></tr>
    </thead>
    <tbody id="employeeTable"></tbody>
  </table>
</div>
  • 搜索框oninput 触发 filterEmployees(),基于前端数组做实时过滤;

  • Bootstrap 校验:通过 needs-validation + novalidate,结合 JS 调用 checkValidity() 控制表单提交;

3.2 前端逻辑核心

// 加载所有员工
function loadEmployees() {
  fetch('/api/employees')
    .then(res => res.json())
    .then(data => {
      employees = data;
      renderTable(employees);
    });
}

// 渲染表格
function renderTable(list) {
  const tbody = document.getElementById('employeeTable');
  tbody.innerHTML = list.map(emp => `
    <tr>
      <td>${emp.id}</td>
      <td>${emp.name}</td>
      <td>${emp.position}</td>
      <td>${emp.email}</td>
      <td>${emp.phone}</td>
      <td>
        <button class="btn btn-sm btn-outline-primary" onclick="startEdit(${emp.id})">编辑</button>
        <button class="btn btn-sm btn-outline-danger" onclick="deleteEmployee(${emp.id})">删除</button>
      </td>
    </tr>
  `).join('');
}
  • CRUD 操作POST/PUT/DELETE 对应 /api/employees 接口;

  • 表单切换:编辑时将数据写回输入框,按钮文本与取消按钮状态动态控制。


四、后端源码解析

4.1 数据库表结构

CREATE TABLE `employee` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(80) NOT NULL,
  `position` VARCHAR(120) NOT NULL,
  `email` VARCHAR(120) NOT NULL,
  `phone` VARCHAR(20) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表';
  • NOT NULL:所有业务字段强制必填;

  • UNIQUE(email):邮箱全局唯一。

4.2 Flask + SQLAlchemy 模型

class Employee(db.Model):
    __tablename__ = 'employee'
    id       = db.Column(db.Integer, primary_key=True)
    name     = db.Column(db.String(80),  nullable=False)
    position = db.Column(db.String(120), nullable=False)
    email    = db.Column(db.String(120), nullable=False, unique=True)
    phone    = db.Column(db.String(20),  nullable=False)

    def to_dict(self):
        return { c.name: getattr(self, c.name) for c in self.__table__.columns }

4.3 RESTful 接口示例

@app.route('/api/employees', methods=['POST'])
def add_employee():
    data = request.get_json() or {}
    # 校验必填
    for f in ('name','position','email','phone'):
        if not data.get(f):
            return jsonify({'error': f'{f} 为必填'}), 400
    emp = Employee(**data)
    db.session.add(emp)
    try:
        db.session.commit()
    except IntegrityError:
        db.session.rollback()
        return jsonify({'error': '邮箱已存在'}), 409
    return jsonify(emp.to_dict()), 201
  • 捕获 IntegrityError:避免重复邮箱导致 500 报错。


五、部署与优化方案

  1. 生产环境:推荐使用 Gunicorn + Nginx 做 WSGI 容器与反向代理,关闭 Flask debug

  2. 配置管理:将数据库 URI、调试开关等放入环境变量或 config.py

  3. 日志:接入 logging 模块或 ELK,记录增删查改操作;

  4. 分页/搜索:后端可扩展服务器端分页或模糊查询,提升数据量大时的性能;

  5. 安全:启用 HTTPS、添加 CSRF 防护(WTForms 或 Flask-WTF)、接口鉴权(Token/JWT)。


六、完整代码:

首先,创建MySQL数据库employee_db,并创建数据表employee。

然后编写后端代码:

# app.py
from flask import Flask, render_template, request, jsonify, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:xiaozeng @localhost/employee_db'      # root:后面是自己的密码,localhost后面就是需要用的数据库
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class Employee(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    position = db.Column(db.String(120))
    email = db.Column(db.String(120), unique=True)
    phone = db.Column(db.String(20))

    def to_dict(self):
        return {
            "id": self.id,
            "name": self.name,
            "position": self.position,
            "email": self.email,
            "phone": self.phone
        }

@app.route('/')
def index():
    return render_template('employee.html')

# 获取所有员工
@app.route('/api/employees', methods=['GET'])
def get_employees():
    employees = Employee.query.all()
    return jsonify([e.to_dict() for e in employees])

# 新增员工
@app.route('/api/employees', methods=['POST'])
def add_employee():
    data = request.get_json()
    new_emp = Employee(
        name=data['name'],
        position=data['position'],
        email=data['email'],
        phone=data['phone']
    )
    db.session.add(new_emp)
    db.session.commit()
    return jsonify(new_emp.to_dict()), 201

# 更新员工
@app.route('/api/employees/<int:id>', methods=['PUT'])
def update_employee(id):
    emp = Employee.query.get_or_404(id)
    data = request.get_json()
    emp.name = data.get('name', emp.name)
    emp.position = data.get('position', emp.position)
    emp.email = data.get('email', emp.email)
    emp.phone = data.get('phone', emp.phone)
    db.session.commit()
    return jsonify(emp.to_dict())

# 删除员工
@app.route('/api/employees/<int:id>', methods=['DELETE'])
def delete_employee(id):
    emp = Employee.query.get_or_404(id)
    db.session.delete(emp)
    db.session.commit()
    return '', 204

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

最后,编写前端代码:

<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>员工信息管理系统</title>
    <!-- 引入 Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        /* 自定义样式 */
        .search-input {
            width: 100%;
            max-width: 400px;
            padding: 6px 8px;
            box-sizing: border-box;
        }
        table tr:hover {
            background: #f1f1f1;
        }
    </style>
</head>
<body>
<div class="container py-4">
    <h1 class="mb-4">员工信息管理系统</h1>

    <!-- 搜索 -->
    <div class="mb-3">
        <input type="text"
               id="search"
               class="form-control search-input"
               placeholder="搜索员工(姓名/职位/邮箱/电话)"
               oninput="filterEmployees()">
    </div>

    <!-- 新增/编辑表单 -->
    <form id="employeeForm" class="row g-2 mb-4 needs-validation" novalidate>
        <div class="col-md-3">
            <input type="text"
                   id="name"
                   class="form-control"
                   placeholder="姓名"
                   required>
            <div class="invalid-feedback">请输入姓名。</div>
        </div>
        <div class="col-md-3">
            <input type="text"
                   id="position"
                   class="form-control"
                   placeholder="职位"
                   required>
            <div class="invalid-feedback">请输入职位。</div>
        </div>
        <div class="col-md-3">
            <input type="email"
                   id="email"
                   class="form-control"
                   placeholder="邮箱"
                   required>
            <div class="invalid-feedback">请输入有效的邮箱地址。</div>
        </div>
        <div class="col-md-3">
            <input type="tel"
                   id="phone"
                   class="form-control"
                   placeholder="电话(11 位数字)"
                   pattern="\d{11}"
                   required>
            <div class="invalid-feedback">请输入 11 位数字电话号码。</div>
        </div>
        <div class="col-auto">
            <button type="button"
                    id="submitBtn"
                    class="btn btn-primary"
                    onclick="submitEmployee()">新增员工</button>
        </div>
        <div class="col-auto">
            <button type="button"
                    id="cancelBtn"
                    class="btn btn-secondary d-none"
                    onclick="cancelEdit()">取消编辑</button>
        </div>
    </form>

    <!-- 员工列表 -->
    <table class="table table-striped">
        <thead>
        <tr>
            <th>ID</th>
            <th>姓名</th>
            <th>职位</th>
            <th>邮箱</th>
            <th>电话</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody id="employeeTable"></tbody>
    </table>
</div>

<!-- 引入 Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
    let employees = [];
    let editingId = null;

    // 启用表单 Bootstrap 校验样式
    (function () {
        'use strict';
        const forms = document.querySelectorAll('.needs-validation');
        Array.from(forms).forEach(form => {
            form.addEventListener('submit', event => {
                if (!form.checkValidity()) {
                    event.preventDefault();
                    event.stopPropagation();
                }
                form.classList.add('was-validated');
            }, false);
        });
    })();

    // 加载员工列表
    function loadEmployees() {
        fetch('/api/employees')
            .then(res => res.json())
            .then(data => {
                employees = data;
                renderTable(employees);
            });
    }

    // 渲染表格
    function renderTable(list) {
        const tbody = document.querySelector('#employeeTable');
        tbody.innerHTML = list.map(emp => `
            <tr>
                <td>${emp.id}</td>
                <td>${emp.name}</td>
                <td>${emp.position}</td>
                <td>${emp.email}</td>
                <td>${emp.phone}</td>
                <td>
                    <button class="btn btn-sm btn-outline-primary me-1" onclick="startEdit(${emp.id})">编辑</button>
                    <button class="btn btn-sm btn-outline-danger" onclick="deleteEmployee(${emp.id})">删除</button>
                </td>
            </tr>
        `).join('');
    }

    // 提交(新增或更新)
    function submitEmployee() {
        const form = document.getElementById('employeeForm');
        if (!form.checkValidity()) {
            form.classList.add('was-validated');
            return;
        }

        const data = {
            name: document.getElementById('name').value.trim(),
            position: document.getElementById('position').value.trim(),
            email: document.getElementById('email').value.trim(),
            phone: document.getElementById('phone').value.trim()
        };

        const method = editingId ? 'PUT' : 'POST';
        const url = editingId ? `/api/employees/${editingId}` : '/api/employees';

        fetch(url, {
            method,
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        }).then(() => {
            resetForm();
            loadEmployees();
        });
    }

    // 开始编辑
    function startEdit(id) {
        const emp = employees.find(e => e.id === id);
        if (!emp) return;
        editingId = id;
        document.getElementById('name').value = emp.name;
        document.getElementById('position').value = emp.position;
        document.getElementById('email').value = emp.email;
        document.getElementById('phone').value = emp.phone;
        document.getElementById('submitBtn').textContent = '保存修改';
        document.getElementById('cancelBtn').classList.remove('d-none');
    }

    // 取消编辑
    function cancelEdit() {
        resetForm();
    }

    // 重置表单
    function resetForm() {
        editingId = null;
        document.getElementById('employeeForm').reset();
        document.getElementById('submitBtn').textContent = '新增员工';
        document.getElementById('cancelBtn').classList.add('d-none');
        document.getElementById('employeeForm').classList.remove('was-validated');
    }

    // 删除员工
    function deleteEmployee(id) {
        if (confirm('确定删除该员工?')) {
            fetch(`/api/employees/${id}`, {method: 'DELETE'})
                .then(loadEmployees);
        }
    }

    // 搜索过滤
    function filterEmployees() {
        const term = document.getElementById('search').value.toLowerCase();
        const filtered = employees.filter(emp =>
            emp.name.toLowerCase().includes(term) ||
            emp.position.toLowerCase().includes(term) ||
            emp.email.toLowerCase().includes(term) ||
            emp.phone.toLowerCase().includes(term)
        );
        renderTable(filtered);
    }

    // 初始化加载
    loadEmployees();
</script>
</body>
</html>

运行结果:

七、总结与展望

本文介绍了一个基于 Flask + MySQL + Bootstrap 的“员工信息管理系统”完整实现,从前端页面、交互逻辑、后端模型到部署优化,覆盖了典型的 CRUD 应用场景。该项目代码量小、逻辑清晰,适合作为入门级示例或公司内部小工具。

未来可进一步迭代:

  • 增加导出 Excel/CSV 功能;

  • 角色权限管理(管理员 vs 普通用户);

  • 接入第三方登录/单点登录;

  • 前端重构:React/Vue + Ant Design 等框架。

欢迎关注、点赞并留言交流,这个是我自己有些用ai写的,后面会慢慢改进!

写在最后:

我们可以在这里学习C++知识:

0voice · GitHub

### 下载包含Python FlaskMySQL项目源码 为了下载并设置一个包含Python Flask框架以及连接到MySQL数据库的项目,可以遵循以下指导: #### 创建虚拟环境 在开始构建此新功能之前,需要更新创建的virtualenv以便能够运行所需的代码,或者为这个项目创建一个新的虚拟环境。激活virtualenv之后,最简单的安装依赖方法是运行如下命令[^1]: ```bash pip install Flask-SQLAlchemy flask-marshmallow marshmallow-sqlalchemy mysqlclient ``` 注意这里增加了`mysqlclient`包用于支持Flask应用与MySQL之间的交互。 #### 初始化基本Flask应用程序结构 对于初始化一个基础的Flask Web服务端程序而言,下面是一个非常典型的例子[^2]: ```python from flask import Flask app = Flask(__name__) @app.route('/') def index(): return '<h1>Hello World</h1>' if __name__ == "__main__": app.run(port=80) ``` 这段代码定义了一个简易的服务器,在根路径返回“Hello World”。 #### 处理Virtualenvwrapper找不到Virtualenv的情况 当遇到virtualenvwrapper无法找到virtualenv的问题时,可以通过将特定版本的virtualenv链接复制到系统的PATH环境中来解决这个问题。具体操作方式是在终端执行下列指令[^3]: ```bash ln -s /usr/local/python3/bin/virtualenv /usr/bin/virtualenv ``` 这会使得系统能够在全局范围内识别到virtualenv命令。 #### 获取闪现内容 关于如何处理Web页面上的临时通知或警告信息(即所谓的“flash messages”),可利用函数`get_flash_messages()`来进行获取,并通过参数控制是否携带类别标签一起取出[^4]. 要实际获得完整的开源项目源码,通常有几种途径可以选择: - 浏览GitHub或其他代码托管平台寻找公开发布的相关案例; - 查阅官方文档中的教程实例; - 加入开发者社区询问是否有推荐的学习资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值