ThinkPHP开发的HR一体化系统:考勤自动统计+工资明细计算+绩效结果归档(含数据库与部署文档)

该文章已生成可运行项目,

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:一套开箱即用的企业人事管理后台,基于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_202310salary_202310appraisal_202310。为什么不用单表加year_month字段?因为当数据量超10万行时,单表WHERE year_month='202310'会触发全表扫描,而分区表查询直接定位到对应物理文件,实测查询速度提升7倍。
- transfer(调动)表是关键枢纽:它不只记录“原岗位→现岗位”,还强制要求填写old_salarynew_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-tablerow-keyexpand-row-keyscustom-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.phpcalculateSalary()方法:

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_cardvarchar(18)而非char(18)。因为身份证末位可能是X,char会补空格导致校验失败。
- attendance_202310.work_daystinyint unsigned(范围0-255),足够覆盖全年工作日,比int节省3字节存储空间。
- salary.total_incomedecimal(12,2)。为什么是12位?因为最大可能值=基本工资999999.99 + 加班费999999.99 = 1999999.98,12位刚好容纳。
- transfer.effect_datedate类型,但程序里强制要求>= 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_mysqlopensslmbstringgd(生成工资条二维码用)。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/:你的业务代码全在这里,controllermodelviewcommand子目录各司其职。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. 检查当前年月是否为202310
2. 查看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理论都管用

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:一套开箱即用的企业人事管理后台,基于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场景中的模块划分与数据联动。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值