PHP+MySQL员工管理系统开发实战:从零搭建企业级CRUD应用
在开发一个企业内部管理系统时员工信息管理往往是核心且高频的需求。无论是初创公司还是成熟企业都需要一个稳定、易用且能自主掌控的系统来管理员工档案、部门、岗位等关键数据。面对市面上功能繁杂的SaaS产品定制化成本高且数据安全存疑许多开发者会选择使用经典的 PHP MySQL 技术栈来自行搭建。这套组合以其部署简单、学习曲线平缓、资源丰富而著称是快速实现一个功能完整的员工管理系统的绝佳选择。本文将手把手带你从零开始构建一个具备增删改查CRUD、分页展示、搜索筛选等核心功能的员工管理系统。我们将从环境搭建讲起逐步深入到数据库设计、前后端编码、安全加固以及部署上线确保每个环节都有清晰的代码示例和原理讲解。无论你是刚接触 PHP 的初学者还是希望重温经典 Web 开发流程的开发者都能通过本文获得一套可直接复用于实际项目的完整解决方案。1. 系统需求分析与技术选型在动手编码之前明确系统要做什么以及选用什么技术来实现是项目成功的第一步。1.1 核心功能需求一个基础的员工管理系统通常需要满足以下功能员工信息管理实现员工基本信息的增加、查看、修改和删除操作。信息展示与查询以列表形式清晰展示所有员工信息并支持按姓名、部门等条件进行筛选和搜索。数据分页当员工数据量较大时分页功能是必不可少的它能提升页面加载速度和用户体验。基础UI与交互需要一个简洁美观的前端界面并能通过表单与后端进行数据交互。1.2 技术栈说明我们选择以下技术组合它们成熟、稳定且拥有庞大的社区支持后端语言PHP ( 7.4)作为服务器端脚本语言PHP 处理表单数据、连接数据库、执行业务逻辑非常高效。我们将使用面向过程的简单写法便于理解。数据库MySQL ( 5.7) / MariaDB关系型数据库用于存储员工、部门等所有结构化数据。前端技术HTML/CSS构建页面结构和样式。为了快速实现美观的界面我们将引入 Bootstrap 5 前端框架。JavaScript处理简单的页面交互如表单验证、确认删除对话框等。服务器任何支持 PHP 的 Web 服务器如 Apache 或 Nginx。本文示例将在 Apache 环境下进行。开发工具任何代码编辑器均可如 VS Code、PHPStorm、Sublime Text 等。这套技术栈的优势在于环境搭建简单学习资源丰富非常适合快速开发中小型 Web 应用。2. 开发环境准备与项目初始化“工欲善其事必先利其器”。一个合适的开发环境能极大提升编码效率和调试体验。2.1 集成环境安装推荐对于初学者或希望快速上手的开发者使用集成环境包是最佳选择它一次性安装了 PHP、MySQL、Apache 和必要的管理工具。Windows 用户推荐使用XAMPP或WampServer。下载安装后启动 Apache 和 MySQL 服务即可。macOS 用户推荐使用MAMP或XAMPP for Mac。Linux 用户可以通过包管理器分别安装apache2,php,mysql-server等软件包。本文将以 XAMPP 为例其默认的网站根目录通常为C:\xampp\htdocs\(Windows) 或/Applications/XAMPP/htdocs(macOS)。2.2 创建项目目录与结构在 Web 服务器的根目录下创建一个名为employee_management的文件夹并在其中规划以下目录结构employee_management/ ├── index.php # 系统首页员工列表展示 ├── create.php # 添加新员工页面 ├── edit.php # 编辑员工信息页面 ├── delete.php # 处理删除员工请求 ├── config/ │ └── database.php # 数据库连接配置文件 ├── includes/ │ └── header.php # 公共头部文件导航栏 │ └── footer.php # 公共底部文件 └── assets/ ├── css/ │ └── style.css # 自定义样式可选 └── js/ └── script.js # 自定义JavaScript可选这种结构将公共部分如数据库连接、页面头尾分离符合“Don‘t Repeat Yourself”原则便于维护。2.3 引入 Bootstrap 5为了快速获得一个响应式、美观的界面我们使用 Bootstrap 的 CDN。在includes/header.php文件中我们将引入 Bootstrap 的 CSS 和 JS。3. 数据库设计与创建数据库是系统的基石良好的设计是后续开发顺利进行的保障。3.1 数据库表设计我们首先创建一个数据库company然后在其中创建两张核心表departments (部门表)存储部门信息。先有部门再有员工这是一种基础的数据关系。employees (员工表)存储员工详细信息并通过department_id字段与部门表关联。3.2 执行 SQL 语句创建表打开 phpMyAdminXAMPP 已集成或任何 MySQL 客户端连接到数据库后执行以下 SQL 语句-- 创建数据库 CREATE DATABASE IF NOT EXISTS company DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE company; -- 创建部门表 CREATE TABLE departments ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 部门名称, description text COMMENT 部门描述, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 插入一些示例部门数据 INSERT INTO departments (name, description) VALUES (技术部, 负责产品研发与技术攻关), (市场部, 负责市场推广与品牌建设), (人力资源部, 负责招聘、培训与员工关系), (财务部, 负责公司财务管理与核算); -- 创建员工表 CREATE TABLE employees ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 员工姓名, email varchar(100) NOT NULL COMMENT 电子邮箱, phone varchar(20) DEFAULT NULL COMMENT 联系电话, department_id int(11) NOT NULL COMMENT 所属部门ID, position varchar(100) DEFAULT NULL COMMENT 职位, salary decimal(10,2) DEFAULT NULL COMMENT 薪水, hire_date date DEFAULT NULL COMMENT 入职日期, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY email (email), KEY department_id (department_id), CONSTRAINT employees_ibfk_1 FOREIGN KEY (department_id) REFERENCES departments (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 插入一些示例员工数据 INSERT INTO employees (name, email, phone, department_id, position, salary, hire_date) VALUES (张三, zhangsanexample.com, 13800138001, 1, 高级软件工程师, 25000.00, 2022-03-15), (李四, lisiexample.com, 13800138002, 2, 市场经理, 18000.00, 2021-08-22), (王五, wangwuexample.com, 13800138003, 3, 招聘专员, 12000.00, 2023-01-10);关键设计点说明字符集使用utf8mb4以支持存储所有 Unicode 字符包括 Emoji。外键约束employees.department_id关联departments.id并设置ON DELETE CASCADE。这意味着当删除一个部门时属于该部门的所有员工记录也会被自动删除。在实际项目中你可能需要根据业务逻辑决定是级联删除还是设置为 NULL。唯一约束为employees.email添加唯一约束确保邮箱不重复。索引为外键字段department_id创建索引能显著提升关联查询的速度。4. 核心功能模块实现接下来我们将一步步实现系统的各个功能页面。首先从所有页面的公共部分和数据库连接开始。4.1 公共配置文件与组件1. 数据库连接配置 (config/database.php)这个文件负责建立与 MySQL 数据库的连接其他 PHP 文件通过require_once引入它来获取数据库连接对象。?php // config/database.php $host localhost; // 数据库主机 $dbname company; // 数据库名 $username root; // 数据库用户名XAMPP默认 $password ; // 数据库密码XAMPP默认为空 try { // 创建 PDO 连接实例 $pdo new PDO(mysql:host$host;dbname$dbname;charsetutf8mb4, $username, $password); // 设置 PDO 错误模式为异常方便调试 $pdo-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 设置默认获取数据的方式为关联数组 $pdo-setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); } catch (PDOException $e) { // 如果连接失败终止脚本并显示错误信息生产环境应记录日志而非直接输出 die(数据库连接失败: . $e-getMessage()); } ?为什么使用 PDOPDO (PHP Data Objects) 是一个数据库访问抽象层它支持多种数据库并且使用参数化查询能有效防止 SQL 注入攻击比老式的mysql_*或mysqli_*函数更安全、更现代。2. 公共头部文件 (includes/header.php)这个文件包含 HTML 文档的头部、导航栏以及引入的公共 CSS/JS 资源。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title员工管理系统/title !-- Bootstrap 5 CSS -- link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.0-alpha1/dist/css/bootstrap.min.css relstylesheet !-- 可选的自定义样式 -- link relstylesheet hrefassets/css/style.css /head body nav classnavbar navbar-expand-lg navbar-dark bg-primary div classcontainer-fluid a classnavbar-brand hrefindex.php员工管理系统/a button classnavbar-toggler typebutton>/div !-- 关闭 .container -- !-- Bootstrap 5 JS Bundle (包含 Popper) -- script srchttps://cdn.jsdelivr.net/npm/bootstrap5.3.0-alpha1/dist/js/bootstrap.bundle.min.js/script !-- 可选的自定义JS -- script srcassets/js/script.js/script /body /html4.2 首页员工列表展示与分页 (index.php)这是系统的门户需要展示所有员工并集成搜索和分页功能。?php // index.php require_once config/database.php; require_once includes/header.php; // 处理搜索和分页参数 $search $_GET[search] ?? ; $page (int)($_GET[page] ?? 1); $limit 10; // 每页显示条数 $offset ($page - 1) * $limit; // 构建基础查询语句和参数 $sql SELECT e.*, d.name as department_name FROM employees e LEFT JOIN departments d ON e.department_id d.id WHERE 11; $params []; if (!empty($search)) { $sql . AND (e.name LIKE :search OR e.email LIKE :search OR d.name LIKE :search); $params[:search] %$search%; } // 获取总记录数用于分页计算 $countStmt $pdo-prepare(str_replace(e.*, d.name as department_name, COUNT(*) as total, $sql)); $countStmt-execute($params); $totalRecords $countStmt-fetch()[total]; $totalPages ceil($totalRecords / $limit); // 添加排序和分页到主查询 $sql . ORDER BY e.created_at DESC LIMIT :limit OFFSET :offset; $params[:limit] $limit; $params[:offset] $offset; // 执行主查询获取当前页数据 $stmt $pdo-prepare($sql); foreach ($params as $key $value) { // PDO 参数绑定需要指定类型 $paramType is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR; $stmt-bindValue($key, $value, $paramType); } $stmt-execute(); $employees $stmt-fetchAll(); ? h2 classmb-4员工列表/h2 !-- 搜索框 -- form methodget classrow g-3 mb-4 div classcol-auto input typetext classform-control namesearch placeholder输入姓名、邮箱或部门... value?php echo htmlspecialchars($search); ? /div div classcol-auto button typesubmit classbtn btn-outline-primary搜索/button ?php if (!empty($search)): ? a hrefindex.php classbtn btn-outline-secondary重置/a ?php endif; ? /div /form !-- 员工列表表格 -- ?php if (empty($employees)): ? div classalert alert-info暂无员工数据。/div ?php else: ? div classtable-responsive table classtable table-hover table-striped thead classtable-dark tr thID/th th姓名/th th邮箱/th th电话/th th部门/th th职位/th th入职日期/th th操作/th /tr /thead tbody ?php foreach ($employees as $emp): ? tr td?php echo $emp[id]; ?/td td?php echo htmlspecialchars($emp[name]); ?/td td?php echo htmlspecialchars($emp[email]); ?/td td?php echo htmlspecialchars($emp[phone]); ?/td td?php echo htmlspecialchars($emp[department_name]); ?/td td?php echo htmlspecialchars($emp[position]); ?/td td?php echo $emp[hire_date]; ?/td td a hrefedit.php?id?php echo $emp[id]; ? classbtn btn-sm btn-warning编辑/a a hrefdelete.php?id?php echo $emp[id]; ? classbtn btn-sm btn-danger onclickreturn confirm(确定要删除员工 ?php echo htmlspecialchars(addslashes($emp[name])); ? 吗此操作不可恢复);删除/a /td /tr ?php endforeach; ? /tbody /table /div !-- 分页导航 -- ?php if ($totalPages 1): ? nav aria-labelPage navigation ul classpagination justify-content-center li classpage-item ?php echo $page 1 ? disabled : ; ? a classpage-link href?page?php echo $page-1; ?search?php echo urlencode($search); ?上一页/a /li ?php for ($i 1; $i $totalPages; $i): ? li classpage-item ?php echo $i $page ? active : ; ? a classpage-link href?page?php echo $i; ?search?php echo urlencode($search); ??php echo $i; ?/a /li ?php endfor; ? li classpage-item ?php echo $page $totalPages ? disabled : ; ? a classpage-link href?page?php echo $page1; ?search?php echo urlencode($search); ?下一页/a /li /ul /nav ?php endif; ? ?php endif; ? ?php require_once includes/footer.php; ?代码关键点解析SQL 注入防护使用 PDO 预处理语句 (prepare) 和参数绑定 (bindValue) 来构建查询确保用户输入的$search参数被安全处理。XSS 防护使用htmlspecialchars()函数对所有从数据库取出并输出到 HTML 页面的数据进行转义防止恶意脚本执行。分页逻辑通过LIMIT和OFFSET实现数据分页。计算总页数 ($totalPages) 来控制分页导航的显示。LEFT JOIN通过连接查询一次性获取员工及其所属部门的名称避免了在循环中多次查询数据库N1 查询问题。4.3 添加员工页面 (create.php)这个页面包含一个表单用于输入新员工的信息。?php // create.php require_once config/database.php; require_once includes/header.php; // 处理表单提交 $errors []; $success false; if ($_SERVER[REQUEST_METHOD] POST) { // 接收并过滤表单数据 $name trim($_POST[name] ?? ); $email trim($_POST[email] ?? ); $phone trim($_POST[phone] ?? ); $department_id (int)($_POST[department_id] ?? 0); $position trim($_POST[position] ?? ); $salary $_POST[salary] ? (float)$_POST[salary] : null; $hire_date $_POST[hire_date] ?? ; // 基础验证 if (empty($name)) $errors[] 姓名不能为空。; if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] 请输入有效的邮箱地址。; if ($department_id 0) $errors[] 请选择所属部门。; // 检查邮箱是否已存在 if (empty($errors)) { $checkStmt $pdo-prepare(SELECT id FROM employees WHERE email ?); $checkStmt-execute([$email]); if ($checkStmt-fetch()) { $errors[] 该邮箱地址已被注册。; } } // 如果没有错误则插入数据库 if (empty($errors)) { $sql INSERT INTO employees (name, email, phone, department_id, position, salary, hire_date) VALUES (?, ?, ?, ?, ?, ?, ?); $stmt $pdo-prepare($sql); try { $stmt-execute([$name, $email, $phone, $department_id, $position, $salary, $hire_date]); $success true; // 成功后可清空表单或重定向 $_POST []; // 清空POST数据 } catch (PDOException $e) { $errors[] 保存数据时发生错误 . $e-getMessage(); } } } // 获取部门列表用于下拉选择 $deptStmt $pdo-query(SELECT id, name FROM departments ORDER BY name); $departments $deptStmt-fetchAll(); ? h2 classmb-4添加新员工/h2 ?php if ($success): ? div classalert alert-success alert-dismissible fade show rolealert 员工信息添加成功 button typebutton classbtn-close>?php // edit.php require_once config/database.php; require_once includes/header.php; $id (int)($_GET[id] ?? 0); if ($id 0) { die(无效的员工ID。); } // 获取当前员工信息 $stmt $pdo-prepare(SELECT * FROM employees WHERE id ?); $stmt-execute([$id]); $employee $stmt-fetch(); if (!$employee) { die(未找到指定的员工。); } // 获取部门列表 $deptStmt $pdo-query(SELECT id, name FROM departments ORDER BY name); $departments $deptStmt-fetchAll(); $errors []; $success false; if ($_SERVER[REQUEST_METHOD] POST) { // 接收数据与create.php类似 $name trim($_POST[name] ?? ); // ... 接收其他字段此处省略参考create.php ... $department_id (int)($_POST[department_id] ?? 0); // 验证与create.php类似但邮箱唯一性检查需排除自己 if (empty($name)) $errors[] 姓名不能为空。; // ... 其他验证 ... if (empty($errors)) { $checkStmt $pdo-prepare(SELECT id FROM employees WHERE email ? AND id ! ?); $checkStmt-execute([$email, $id]); if ($checkStmt-fetch()) { $errors[] 该邮箱地址已被其他员工使用。; } } // 更新数据 if (empty($errors)) { $sql UPDATE employees SET name?, email?, phone?, department_id?, position?, salary?, hire_date? WHERE id?; $stmt $pdo-prepare($sql); try { $stmt-execute([$name, $email, $phone, $department_id, $position, $salary, $hire_date, $id]); $success true; // 更新成功后重新查询最新数据用于表单显示 $stmt $pdo-prepare(SELECT * FROM employees WHERE id ?); $stmt-execute([$id]); $employee $stmt-fetch(); } catch (PDOException $e) { $errors[] 更新数据时发生错误 . $e-getMessage(); } } } ? h2 classmb-4编辑员工信息/h2 !-- 成功/错误提示与create.php类似此处省略 -- form methodpost action !-- 表单字段value值从$employee数组中获取 -- div classrow g-3 div classcol-md-6 label forname classform-label姓名 */label input typetext classform-control idname namename value?php echo htmlspecialchars($employee[name]); ? required /div div classcol-md-6 label foremail classform-label邮箱 */label input typeemail classform-control idemail nameemail value?php echo htmlspecialchars($employee[email]); ? required /div !-- ... 其他字段参考create.php注意value值的来源 ... -- div classcol-md-6 label fordepartment_id classform-label所属部门 */label select classform-select iddepartment_id namedepartment_id required option value请选择部门/option ?php foreach ($departments as $dept): ? option value?php echo $dept[id]; ? ?php echo ($employee[department_id] $dept[id]) ? selected : ; ? ?php echo htmlspecialchars($dept[name]); ? /option ?php endforeach; ? /select /div /div div classmt-4 button typesubmit classbtn btn-primary更新信息/button a hrefindex.php classbtn btn-secondary返回列表/a /div /form ?php require_once includes/footer.php; ?4.5 删除员工处理 (delete.php)删除操作通常通过一个独立的脚本处理它不包含 HTML 界面只负责执行删除并重定向。?php // delete.php require_once config/database.php; // 验证请求方法并获取ID if ($_SERVER[REQUEST_METHOD] ! GET || !isset($_GET[id])) { header(Location: index.php); exit; } $id (int)$_GET[id]; if ($id 0) { header(Location: index.php); exit; } // 执行删除操作 try { $stmt $pdo-prepare(DELETE FROM employees WHERE id ?); $stmt-execute([$id]); // 可以根据$stmt-rowCount()判断是否成功删除 } catch (PDOException $e) { // 记录错误日志生产环境不应直接输出给用户 error_log(删除员工失败 (ID: $id): . $e-getMessage()); } // 无论成功与否都重定向回列表页 header(Location: index.php); exit; ?关键点解析安全性脚本开头验证了请求方法和参数防止直接访问或无效ID导致错误。无界面操作删除是敏感操作此脚本不输出任何 HTML执行后立即重定向这符合 PRG 模式避免刷新页面导致重复删除。错误处理使用try...catch捕获可能的数据异常如外键约束冲突并记录到错误日志而不是显示给用户。5. 系统安全加固与最佳实践一个可用的系统是基础一个安全的系统才能投入实际使用。以下是几个必须关注的安全和工程化要点。5.1 关键安全措施SQL 注入防护我们已经全程使用 PDO 预处理语句这是最有效的防护手段。绝对不要将用户输入直接拼接到 SQL 字符串中。XSS 跨站脚本防护在所有将数据库数据输出到 HTML 的地方都使用了htmlspecialchars()函数。CSRF 防护本示例未实现但对于重要操作如删除、修改密码应添加 CSRF Token。可以在表单中生成一个随机 Token 存入 Session提交时进行验证。会话安全如果后续添加登录功能务必使用 PHP 内置的session_start()并配置安全的 Session 参数如session.cookie_httponly 1。文件上传如果系统需要上传员工照片等文件必须进行严格的验证检查文件类型MIME Type、重命名文件、限制上传目录不可执行、将文件存储在 Web 根目录之外。5.2 代码结构与可维护性建议分离业务逻辑与表现层当前代码将 PHP 逻辑和 HTML 混写对于小型项目尚可。对于更复杂的项目建议采用简单的 MVC 模式或将数据库操作封装成独立的函数或类。错误处理与日志生产环境中应将display_errors设置为Off并将log_errors设置为On将错误记录到日志文件而不是显示给用户。配置信息管理将数据库连接信息等敏感配置放在config/database.php是好的开始但应确保该文件不在 Web 可访问目录下或通过.htaccess禁止直接访问。使用 Composer 管理依赖随着项目增长你可能会引入第三方库如用于验证的respect/validation用于模板的Twig。使用 Composer 进行依赖管理是 PHP 项目的标准做法。5.3 功能扩展方向一个基础的 CRUD 系统可以沿多个方向扩展用户认证与权限增加登录/注销功能区分管理员和普通用户权限。数据导出添加将员工列表导出为 Excel 或 PDF 的功能。图表统计使用 Chart.js 等库在仪表盘展示部门人数统计、薪资分布等图表。API 接口将后端逻辑改造成 RESTful API供前端单页面应用如 Vue.js, React调用。批量操作在列表页增加复选框支持批量删除或导出。历史记录创建日志表记录对员工数据的增删改操作。6. 部署上线与常见问题排查开发完成后你需要将系统部署到真实的服务器上。6.1 部署流程简述准备服务器购买云服务器如阿里云 ECS、腾讯云 CVM安装 LAMP (Linux, Apache, MySQL, PHP) 或 LNMP 环境。上传代码使用 FTP如 FileZilla或 Git 将你的项目代码上传到服务器的 Web 目录如/var/www/html/。配置数据库在服务器上创建 MySQL 数据库和用户导入本地导出的 SQL 文件包含表结构和数据。修改配置文件更新config/database.php中的连接信息指向服务器的数据库。调整文件权限确保 Web 服务器用户如www-data对项目目录有适当的读写权限通常files和cache目录需要写权限。配置域名可选如果你有域名将其解析到服务器 IP并在 Web 服务器配置中绑定。6.2 常见问题与解决方案问题现象可能原因解决思路页面显示空白PHP 语法错误或致命错误1. 检查php.ini中display_errors是否开启。2. 查看 Web 服务器错误日志如 Apache 的error.log。3. 在代码开头添加error_reporting(E_ALL); ini_set(‘display_errors’, 1);临时开启错误显示。数据库连接失败1. 数据库配置信息错误。2. MySQL 服务未运行。3. 服务器防火墙阻止了端口默认3306。4. MySQL 用户权限不足。1. 仔细核对config/database.php中的主机、用户名、密码、数据库名。2. 运行systemctl status mysql检查服务状态。3. 确保 MySQL 监听地址bind-address不是127.0.0.1如果PHP和MySQL不在同一台机器。4. 在 MySQL 中为用户授予远程或本地连接权限。页面提示“未定义变量”或“未定义索引”PHP 错误报告级别设置问题代码中使用了未初始化的数组键或变量。1. 这是一种警告不影响运行但影响体验。确保在使用$_GET[‘id’]等变量前使用isset()或??空合并运算符进行判断如$id $_GET[‘id’] ?? 0;。2. 可以在生产环境关闭E_NOTICE级别的错误报告。中文乱码数据库、PHP 文件、HTML 页面的字符集不统一。1. 确保数据库、表、字段的字符集为utf8mb4。2. 在 PHP 连接数据库后执行SET NAMES ‘utf8mb4’语句PDO 的 DSN 中已设置charsetutf8mb4。3. 确保 PHP 文件本身以 UTF-8 without BOM 格式保存。4. HTML 页面meta charset”UTF-8″。删除或更新操作无效SQL 语句执行失败但未捕获异常或外键约束阻止操作。1. 在delete.php和edit.php的数据库操作周围添加try…catch并记录或输出错误信息以调试。2. 检查外键约束例如尝试删除一个有员工的部门。至此一个功能完整、具备基本安全性的 PHP MySQL 员工管理系统就搭建完成了。从环境配置、数据库设计到前后端功能实现我们覆盖了 Web 应用开发的核心流程。这个项目不仅是一个可运行的系统更是一个理解 MVC 模式雏形、掌握 PHP 数据库操作和安全编程的绝佳练习。你可以在此基础上根据前面提到的扩展方向继续深化功能将其打造成一个更符合实际业务需求的管理工具。