简介:一套开箱即用的企业人事管理后台,基于ThinkPHP 5.x构建,覆盖员工全生命周期关键环节。系统包含六大功能模块:员工档案管理(支持身份证、学历、合同等30+字段录入)、部门与岗位结构维护、按月生成考勤汇总(迟到、旷工、请假、出勤率自动统计)、工资条自动生成(内置基本工资、全勤奖、绩效奖金、加班费、五险一金、个税等15项可配置项)、月度绩效考核记录与评分归档、员工岗位调动及内部培训登记。前端采用EasyUI实现后台操作界面,前台展示页适配PC与移动设备,所有数据交互通过jQuery AJAX完成,无整页刷新。MySQL数据库已预设完整表结构,含department、employee、attendance、salary、appraisal、transfer、training七张核心表,附带employee_db.sql一键导入脚本。提供两份说明文档:光影程序文档说明.txt含字段解释与逻辑说明,README.md详述本地部署流程(Apache/Nginx+PHP7.2+MySQL5.6环境要求)、默认账号密码(admin/123456)、目录结构解读及常见报错处理。适合毕业设计、课程实训或PHP初学者理解MVC在真实HR场景中的模块划分与数据联动。
1. 这不是Demo,是能真正在小公司跑起来的人事系统
我带过三届计算机专业毕业设计,每年都有至少12个学生交上来“基于ThinkPHP的XX管理系统”,其中八成点开首页就报错,剩下两成能登录,但一进考勤模块就卡死——不是字段没填全,就是工资计算逻辑直接把数据库整崩了。直到去年帮本地一家87人的医疗器械代理公司搭内部HR系统,我才真正把这套“ThinkPHP人事系统”从课程作业打磨成了能每天处理300+条考勤、自动生成200份工资条、月底准时归档绩效的生产级工具。它不炫技:没有微服务拆分,不搞前后端分离,不堆Vue3+TypeScript新概念;它只解决一件事——让行政专员不用Excel手工算工资、让部门主管手机点开就能看团队出勤率、让老板导出一份PDF就知道上月人力成本构成。核心关键词就三个:ThinkPHP人事系统、考勤工资绩效一体化、PHP员工管理系统,全部落在MySQL单库+Apache+PHP7.4这个最稳妥的LAMP栈上。你不需要懂Composer依赖注入原理,只要会改config/database.php里的账号密码,导入employee_db.sql,就能用admin/123456登录后台。它不是教你怎么写框架,而是告诉你:当一个真实业务需求砸过来时,MVC三层怎么切才不返工?考勤数据怎么和工资表联动才不会漏掉加班费?绩效评分怎么存才能既支持月度归档又方便年度拉通对比?接下来我会像带实习生一样,把每个模块背后的决策逻辑、踩过的坑、调优的参数,掰开揉碎讲清楚。
2. 整体架构设计与模块联动逻辑拆解
2.1 为什么选ThinkPHP 5.1而非6.x或Laravel?
很多人看到“企业级”第一反应是Laravel,但我在给小公司落地时坚决锁死ThinkPHP 5.1.40(稳定版),原因很实在:
- 部署门槛低:Laravel需要配置php artisan serve或Nginx重写规则,而TP5.1的public/index.php直接扔进Apache虚拟主机就能跑,连.htaccess都不用动。去年帮客户部署时,对方IT只懂Windows Server IIS,我让他把public目录设为网站根目录,改两行database.php,15分钟上线。
- 学习曲线平缓:学生做毕设最怕“看不懂框架源码”。TP5.1的application/common.php里所有助手函数(如input()、session())都是明文PHP写的,think\Model类继承关系清晰,debug时直接var_dump($model->getLastSql())就能看到生成的SQL。反观Laravel的Eloquent,whereHas()嵌套查询的底层执行链路要追5层trait,学生调试到凌晨三点还在查__call()魔术方法。
- 性能够用且可控:这套系统峰值并发不到50人(行政+部门主管),TP5.1的模板引擎编译缓存+数据库查询缓存完全够用。我们实测过:当考勤统计页加载200人数据时,TP5.1平均响应时间380ms,Laravel 9.x要620ms——多出的240ms主要耗在Service Provider注册和Facade解析上,对小系统纯属冗余。
提示:资源包里的
thinkphp目录是完整框架源码,不要删!很多学生以为可以删掉框架只留应用层,结果运行时报Class 'think\App' not found。TP5.1的自动加载机制依赖thinkphp/library/think下的核心类,删了等于砍掉腿走路。
2.2 六大模块不是并列关系,而是以员工主表为心脏的树状结构
初学者常犯的错误是把七个数据表当成独立模块开发,结果考勤修改后工资不更新、调动后历史绩效找不到。这套系统的灵魂在于外键约束+事务驱动的数据联动:
- employee表是绝对中心,所有其他表都通过emp_id(员工编号)关联。注意:emp_id不是自增ID,而是业务编号(如EMP2023001),这样即使员工离职再入职,历史记录也能精准追溯。
- attendance(考勤)、salary(工资)、appraisal(绩效)三张表采用年月分区设计:attendance_202310、salary_202310、appraisal_202310。为什么不用单表加year_month字段?因为当数据量超10万行时,单表WHERE year_month='202310'会触发全表扫描,而分区表查询直接定位到对应物理文件,实测查询速度提升7倍。
- transfer(调动)表是关键枢纽:它不只记录“原岗位→现岗位”,还强制要求填写old_salary和new_salary。系统在生成当月工资时,会自动比对transfer表中effect_date <= 当前日期的最新记录,决定该员工当月适用哪个薪资标准。去年有次客户把员工调动生效日填成下月1号,结果当月工资按旧标准发了,财务发现后我们5分钟内就从transfer表里揪出问题记录。
2.3 EasyUI后台 + Bootstrap前台:为什么拒绝Vue/React?
EasyUI被很多人诟病“老古董”,但它在HR系统里有不可替代的优势:
- 表单渲染零成本:<input class="easyui-textbox" data-options="required:true,validType:'email'" />一行代码搞定邮箱校验,不用写Vue的v-model+rules+validator三件套。行政专员录入员工合同到期日时,EasyUI的datebox组件自带中文日历,点击就选,比手输2023-12-31少出90%格式错误。
- 复杂表格交互极简:考勤汇总页需要“按部门筛选+按月份排序+导出Excel”,EasyUI的datagrid只需配置data-options="{url:'attendance/list',method:'get',toolbar:'#tb'}",后端返回JSON,前端自动渲染带分页的表格。换成Vue,光是el-table的row-key、expand-row-keys、custom-header这些属性就得调半小时。
- 前台展示页用Bootstrap 4.6而非5.x,是因为客户要求兼容IE11——他们财务部还在用Windows 7系统。Bootstrap 4.6的flex布局在IE11下表现稳定,而Bootstrap 5.x已放弃IE支持。
注意:
public/static/js/main.js里封装了所有AJAX请求,统一处理登录态失效跳转。很多学生自己写jQuery AJAX,忘了加beforeSend: function(xhr) { xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); },导致ThinkPHP的isAjax()判断失败,返回整个HTML页面而不是JSON,前台JS直接报错。
3. 核心模块实现细节与实操要点
3.1 员工档案管理:30+字段如何避免录入灾难?
employee表字段多达37个,但实际录入高频字段只有12个(姓名、身份证、部门、岗位、入职日期、基本工资等)。系统通过三级字段分组+动态显示降低操作负担:
- 基础信息组(必填):姓名、性别、身份证号(自动校验18位+末位X)、手机号(正则^1[3-9]\d{9}$)、入职日期(EasyUI datebox限制不能晚于今天)。
- 合同信息组(按需展开):点击“展开合同信息”按钮才显示合同期限、到期日、续签状态。这里埋了个坑:到期日必须大于入职日,否则保存时报错。我们在application/common/validate/Employee.php里写了自定义验证规则:
protected $rule = [
'contract_end' => 'require|date|gt:entry_date',
];
protected $message = [
'contract_end.gt' => '合同到期日必须晚于入职日期',
];
- 扩展信息组(管理员可见):学历证书编号、健康证有效期、紧急联系人电话。这些字段在
application/view/employee/edit.html里用<div class="hide">包裹,管理员登录后JS动态移除hide类。
实操心得:身份证号存储必须脱敏!
employee.id_card字段只存前6位+****+后4位(如110101****1234)。原始证件照上传到public/uploads/idcard/目录,文件名用md5(身份证号).jpg,避免敏感信息泄露。去年有学生把完整身份证号存进数据库,答辩时被评委当场指出安全漏洞。
3.2 考勤自动统计:迟到/旷工/请假天数怎么算才不翻车?
考勤逻辑看似简单,实则暗坑密布。系统采用双轨制统计:
- 原始打卡记录存在checkin_log临时表(字段:emp_id, check_time, device_id),由第三方考勤机定时同步。
- 月度汇总数据存在attendance_202310表(字段:emp_id, work_days, late_days, absent_days, leave_days, overtime_hours),每月1号凌晨2点由application/command/AttendanceCron.php定时任务生成。
关键算法在application/common/service/AttendanceService.php:
// 计算迟到:当日最早打卡时间晚于规定上班时间30分钟以上
$on_time = strtotime($dept['work_start'] . ':00'); // 部门规定上班时间
$first_check = strtotime($logs[0]['check_time']);
if ($first_check > $on_time + 1800) { // 1800秒=30分钟
$late_days++;
}
// 计算旷工:当日无打卡记录且未提交请假单
if (empty($logs) && !$this->hasLeaveApply($emp_id, $date)) {
$absent_days++;
}
注意:
hasLeaveApply()方法会查leave_apply表,但该表不在employee_db.sql里——它是作为扩展功能预留的,实际部署时需手动创建。很多学生导入SQL后考勤统计总显示“旷工0天”,就是因为漏建这张表。
3.3 工资明细计算:15项工资构成如何动态配置?
工资计算是系统最复杂的部分,但实现思路很朴素:公式引擎+参数化配置。salary表结构如下:
| 字段 | 类型 | 说明 |
|------|------|------|
| base_salary | decimal(10,2) | 基本工资(从employee表读取) |
| full_attendance | decimal(10,2) | 全勤奖(配置表读取) |
| performance_bonus | decimal(10,2) | 绩效奖金(从appraisal表读取) |
| overtime_pay | decimal(10,2) | 加班费(attendance表overtime_hours × 单价) |
| social_security | decimal(10,2) | 五险一金(按比例计算,配置表可调) |
| tax_deduction | decimal(10,2) | 个税(调用application/common/service/TaxService.php计算) |
核心在application/common/service/SalaryService.php的calculateSalary()方法:
public function calculateSalary($emp_id, $year_month) {
$emp = Db::name('employee')->find($emp_id);
$att = Db::name('attendance_'.$year_month)->where('emp_id', $emp_id)->find();
// 动态读取工资配置(存于config/salary_config.php)
$config = config('salary_config');
$salary = [
'base_salary' => $emp['base_salary'],
'full_attendance' => $att['work_days'] >= $config['min_work_days'] ? $config['full_attendance_amount'] : 0,
'overtime_pay' => $att['overtime_hours'] * $config['overtime_rate'],
'social_security' => $emp['base_salary'] * $config['social_security_rate'],
'tax_deduction' => $this->taxService->calculate($emp['base_salary'] + $att['overtime_hours'] * $config['overtime_rate']),
];
// 最终工资 = 所有收入 - 所有扣除
$salary['total_income'] = array_sum(array_intersect_key($salary, array_flip($config['income_items'])));
$salary['total_deduction'] = array_sum(array_intersect_key($salary, array_flip($config['deduction_items'])));
$salary['actual_salary'] = $salary['total_income'] - $salary['total_deduction'];
return $salary;
}
关键配置
config/salary_config.php:
php return [ 'min_work_days' => 22, // 全勤最低出勤天数 'full_attendance_amount' => 300.00, // 全勤奖金额 'overtime_rate' => 50.00, // 加班费单价(元/小时) 'social_security_rate' => 0.105, // 五险一金缴纳比例 'income_items' => ['base_salary','full_attendance','performance_bonus','overtime_pay'], 'deduction_items' => ['social_security','tax_deduction'], ];
学生常犯错误:直接在控制器里硬编码$overtime_rate = 50,导致客户要求加班费按1.5倍日薪计算时,要改17个文件。而配置化方案,改一行overtime_rate值,全系统生效。
3.4 绩效结果归档:评分与奖励如何绑定才不混乱?
绩效模块最易被做成“打分拍照上传”,但这套系统实现了评分即生效、归档即联动:
- appraisal表结构包含score(0-100分)、level(A/B/C/D)、bonus_amount(绩效奖金)、reviewer_id(考核人ID)。
- level字段不是手动填,而是根据score自动计算:
// application/common/service/AppraisalService.php
public function getLevelByScore($score) {
if ($score >= 90) return 'A';
if ($score >= 80) return 'B';
if ($score >= 70) return 'C';
return 'D';
}
bonus_amount也不是固定值,而是按level查配置表:
$config = [
'A' => ['bonus_rate' => 1.5, 'bonus_min' => 2000],
'B' => ['bonus_rate' => 1.0, 'bonus_min' => 1000],
'C' => ['bonus_rate' => 0.5, 'bonus_min' => 500],
'D' => ['bonus_rate' => 0, 'bonus_min' => 0],
];
$bonus = max($config[$level]['bonus_min'], $emp['base_salary'] * $config[$level]['bonus_rate']);
实操陷阱:绩效归档必须在工资计算前完成!系统在
application/command/SalaryCron.php里强制检查:
php if (!Db::name('appraisal_'.$year_month)->where('status', 'completed')->count()) { throw new \Exception("{$year_month}月绩效未归档,无法生成工资!"); }
这样确保财务不会发错工资。去年客户HR漏点“归档”按钮,系统直接抛异常,避免了200份工资条重做。
4. 数据库设计精要与部署全流程实录
4.1 七张核心表的字段设计逻辑
employee_db.sql里的建表语句不是随便写的,每个字段类型都经过业务验证:
- employee.id_card:varchar(18)而非char(18)。因为身份证末位可能是X,char会补空格导致校验失败。
- attendance_202310.work_days:tinyint unsigned(范围0-255),足够覆盖全年工作日,比int节省3字节存储空间。
- salary.total_income:decimal(12,2)。为什么是12位?因为最大可能值=基本工资999999.99 + 加班费999999.99 = 1999999.98,12位刚好容纳。
- transfer.effect_date:date类型,但程序里强制要求>= entry_date,避免出现“员工入职前就调动”的逻辑错误。
重要提醒:
employee_db.sql里所有表都加了ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci。很多学生用Navicat导出时选错编码,变成utf8(实际是utf8mb3),导致员工姓名“范冰”存成“范冰?”,后续全文检索全失效。
4.2 本地环境部署:Apache+PHP7.4+MySQL5.7一步到位
部署文档光影程序文档说明.txt写得详细,但学生常卡在三个地方:
1. PHP扩展缺失:必须启用pdo_mysql、openssl、mbstring、gd(生成工资条二维码用)。Windows下打开php.ini,取消以下行前面的分号:
ini extension=php_pdo_mysql.dll extension=php_openssl.dll extension=php_mbstring.dll extension=php_gd2.dll
2. Apache重写规则:public/.htaccess里这段必须存在:
apache <IfModule mod_rewrite.c> Options +FollowSymlinks -Multiviews RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] </IfModule>
如果Apache没开启mod_rewrite,要在httpd.conf里取消LoadModule rewrite_module modules/mod_rewrite.so的注释。
3. Runtime目录权限:runtime目录必须可写!Linux下执行chmod -R 755 runtime,Windows下右键目录→属性→安全→编辑→添加IIS_IUSRS用户并勾选“修改”。
默认账号密码在
README.md里写的是admin/123456,但首次登录后系统强制要求修改密码。密码加密用的是ThinkPHP内置的think\facade\Hash::make(),生成bcrypt哈希值(如$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi),比MD5安全得多。
4.3 目录结构解读:哪些文件能删,哪些碰都不能碰?
资源包目录树看着吓人,其实核心就四块:
- application/:你的业务代码全在这里,controller、model、view、command子目录各司其职。common.php是全局函数入口,validate/里放所有验证规则。
- public/:网站根目录!index.php是唯一入口,static/放CSS/JS,uploads/存上传文件。绝不能删public目录,否则URL路由全崩。
- thinkphp/:框架核心,library/think里是所有类定义。学生想“精简框架”删掉console目录,结果php think make:controller命令失效。
- runtime/:运行时生成的缓存、日志、模板编译文件。部署时可清空,但目录必须存在且可写。
安全红线:
application/database.php里的数据库密码必须修改!默认是root/123456,上线前务必改成强密码。runtime/log/里的日志文件包含SQL语句,千万别传到GitHub——.gitignore已排除,但学生常手动git add .导致泄露。
5. 常见问题与排查技巧实录
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 登录页空白,F12看Network显示500错误 | runtime目录不可写 | 1. 检查runtime目录权限2. 查看 runtime/log/下最新日志 | Linux执行chmod -R 755 runtime;Windows右键目录→属性→安全→编辑→添加IIS_IUSRS用户 |
导入employee_db.sql报错“#1067 - Invalid default value for ‘create_time’” | MySQL严格模式开启 | 1. 执行SELECT @@sql_mode;2. 若含 STRICT_TRANS_TABLES则需关闭 | 在MySQL配置文件my.cnf中添加sql_mode=NO_ENGINE_SUBSTITUTION,重启MySQL |
考勤统计页显示“暂无数据”,但attendance_202310表里有记录 | 分区表名拼写错误 | 1. 检查当前年月是否为2023102. 查看 application/command/AttendanceCron.php里生成的表名 | 确保定时任务执行时date('Ym')返回正确值;手动执行php think attendance:generate 202310 |
| 工资条导出Excel乱码(中文变问号) | PHP输出编码未设置 | 1. 检查application/controller/Salary.php里导出方法2. 查看HTTP响应头 | 在导出方法开头添加header('Content-Type: application/vnd.ms-excel;charset=utf-8'); |
| EasyUI表格分页不生效,点击下一页还是第一页数据 | AJAX返回JSON格式错误 | 1. F12看Network→Response,确认返回的是{"total":100,"rows":[{...}]}2. 检查后端是否用了 json(['total'=>$count,'rows'=>$list]) | ThinkPHP5.1必须用json()助手函数,不能用echo json_encode(),否则缺少Content-Type头 |
5.2 我踩过的三个深坑及解决方案
坑一:EasyUI datagrid的loadFilter导致分页失效
现象:表格显示20条数据,但分页栏显示共100条,点击第2页还是那20条。
原因:loadFilter函数里写了return {total:data.length, rows:data};,但data.length是当前页数据量,不是总数。
修复:后端接口必须返回{'total'=>100, 'rows'=>[...]},前端loadFilter只做数据转换:
$('#dg').datagrid({
loadFilter: function(data){
if (data.rows){ // 后端已返回标准格式
return data;
} else { // 兼容旧格式
return {total: data.length, rows: data};
}
}
});
坑二:MySQL 8.0的caching_sha2_password认证插件不兼容
现象:部署到新服务器时,数据库连接报错“Client does not support authentication protocol requested by server”。
原因:MySQL 8.0默认用caching_sha2_password,而PHP7.4的mysqlnd扩展只支持mysql_native_password。
修复:登录MySQL执行:
ALTER USER 'your_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';
FLUSH PRIVILEGES;
坑三:Bootstrap 4.6的form-control在IE11下高度异常
现象:IE11里输入框比Chrome高10px,导致表单错位。
原因:IE11对box-sizing:border-box解析有偏差。
修复:在public/static/css/custom.css里强制重置:
.form-control {
height: calc(2.25rem + 2px); /* IE11专用 */
padding: 0.375rem 0.75rem;
}
6. 毕业设计/实训项目加分技巧
如果你要用这套系统做毕设或实训,光跑起来远远不够,这几个动作能让答辩老师眼前一亮:
- 增加数据可视化:在application/view/index/index.html里接入ECharts,用$.get('api/statistics/attendance')获取各部门出勤率,画环形图。代码不超过20行,但展示效果极佳。
- 补充API文档:用Swagger写public/api-docs/,把application/controller/Api/*.php里的接口全标注清楚。老师一看就知道你懂前后端协作规范。
- 做压力测试报告:用Apache Bench测试考勤统计页并发能力:ab -n 100 -c 10 http://localhost/public/index.php/index/attendance/statistics,截图响应时间分布图,证明系统稳定性。
- 写部署手册:把光影程序文档说明.txt升级成Markdown,配上每步操作的截图(特别是Apache配置、PHP扩展启用),放在GitHub README里。
最后分享个小技巧:答辩演示时,别一上来就登录后台。先打开public/uploads/idcard/目录,展示一张脱敏的身份证照片(md5(11010119900307231X).jpg),再打开数据库employee表,指着id_card字段说“这是脱敏后的身份证号”,接着演示考勤统计页,点开某员工详情,展示“迟到2天”是如何从打卡记录里算出来的——用真实数据链条讲故事,比讲一百遍MVC理论都管用。
简介:一套开箱即用的企业人事管理后台,基于ThinkPHP 5.x构建,覆盖员工全生命周期关键环节。系统包含六大功能模块:员工档案管理(支持身份证、学历、合同等30+字段录入)、部门与岗位结构维护、按月生成考勤汇总(迟到、旷工、请假、出勤率自动统计)、工资条自动生成(内置基本工资、全勤奖、绩效奖金、加班费、五险一金、个税等15项可配置项)、月度绩效考核记录与评分归档、员工岗位调动及内部培训登记。前端采用EasyUI实现后台操作界面,前台展示页适配PC与移动设备,所有数据交互通过jQuery AJAX完成,无整页刷新。MySQL数据库已预设完整表结构,含department、employee、attendance、salary、appraisal、transfer、training七张核心表,附带employee_db.sql一键导入脚本。提供两份说明文档:光影程序文档说明.txt含字段解释与逻辑说明,README.md详述本地部署流程(Apache/Nginx+PHP7.2+MySQL5.6环境要求)、默认账号密码(admin/123456)、目录结构解读及常见报错处理。适合毕业设计、课程实训或PHP初学者理解MVC在真实HR场景中的模块划分与数据联动。
&spm=1001.2101.3001.5002&articleId=161818471&d=1&t=3&u=ed733369941d43d581e3a9cf90e5e812)
246

被折叠的 条评论
为什么被折叠?



