
简介这是一份基于ThinkPHP框架开发的公司员工考勤工资考核系统毕业设计源码适合计算机相关专业学生及PHP开发者用于毕设参考、课程设计或项目实战。系统围绕公司核心人事业务覆盖部门员工管理、员工调动、工资核算、考核、考勤和培训共六个模块后台采用EasyUI、前台采用Bootstrap并全程配合jQuery异步加载能呈现较完整的前后端交互流程。压缩包内共1429个文件核心为212个PHP后端文件配套299个HTML、277个JS和238个CSS等前端页面样式文件同时包含2个SQL数据库脚本及说明文档整体大小约6.26MB目录层级清晰便于对照学习。资源已有451人学习浏览适合需要快速理解MVC结构、熟悉PHP组织人事系统数据库设计和前后端衔接的读者。借助该系统的表结构设计、业务逻辑拆分和后台管理界面搭建思路可通过SQL脚本还原数据库表结构与初始数据快速搭建运行环境并在此基础上扩展或二次开发。无论是课程作业、毕业设计还是入职前练手都具备较好参考价值。1. 考勤工资系统为什么值得用ThinkPHP拆一遍做过公司内部系统的人都有体会考勤统计和工资核算这两件事看着是表格问题实际是流程问题。考勤数据散在打卡机、Excel、微信群里月底汇总时经常对不上工资又依赖考勤、考核、调动记录任何一个环节漏了实发数就算错。这套基于ThinkPHP MVC框架实现的员工考勤工资考核系统把部门、员工、调动、考勤、考核、工资、培训这七类数据沉淀成MySQL里互相可关联的表后台用EasyUI做管理界面前台用Bootstrap展示全程走jQuery异步加载。对做毕业设计和课程设计的IT学生来说它是一个能直接跑起来的完整CRUD案例对在职开发来说它是理解“规则引擎式工资计算”和“多维关联查询数据库”的现成素材。下面按数据设计、权限、核心业务、部署排错这条线拆开讲。2. 数据表设计与关联查询先把七张表的边界划清楚2.1 从字段反推业务边界拿到源码第一步不是看控制器而是打开数据库看表结构。这套系统的核心表可以分成三类主数据部门、员工、过程数据考勤、考核、调动、结果数据工资。如果表边界没划清后面写查询会很痛苦。表关键字段业务含义department部门编号、部门名称、添加时间组织架构最小单位member员工编号、密码、姓名、性别、部门、担任职务、基本工资员工主档部门用外键引用transfer调动id、姓名、原基本工资、调动职位、调动部门、调动日期记录岗位变动轨迹attendance考勤id、姓名、到勤天数、迟到天数、旷工天数、请假天数按月汇总的考勤快照appraisal考核id、考核结果、考核奖励、考核部门、考核人月度绩效考核结果salary工资id、基本工资、全勤奖励、考核奖励、加班工资、津贴补助、惩罚金额、个人所得税、五险一金、应发工资、实发工资最终薪资结算training培训id、培训名称、培训时间、培训地点培训记录注意这里的设计思路考勤、考核、工资都带“年份 月份”字段这是典型的月度汇总表设计。好处是统计某月数据时直接WHERE year ? AND month ?避免在明细数据上做临时聚合查询数据库时性能更稳。2.2 建表SQL与索引选择源码里的表结构简化后如下。手动敲一遍能加深对字段类型选择的理解CREATE TABLE member ( id int(11) NOT NULL AUTO_INCREMENT, member_code varchar(20) NOT NULL COMMENT 员工编号, password varchar(64) NOT NULL COMMENT 登录密码, name varchar(30) NOT NULL, sex tinyint(1) DEFAULT 1, department_id int(11) NOT NULL COMMENT 所属部门ID, position varchar(50) DEFAULT COMMENT 担任职务, basic_salary decimal(10,2) DEFAULT 0.00 COMMENT 基本工资, add_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_member_code (member_code), KEY idx_department (department_id) ) ENGINEInnoDB DEFAULT CHARSETutf8 COMMENT员工表;CREATE TABLE salary ( id int(11) NOT NULL AUTO_INCREMENT, member_id int(11) NOT NULL, year smallint(6) NOT NULL, month tinyint(4) NOT NULL, basic_salary decimal(10,2) DEFAULT 0.00, attendance_bonus decimal(10,2) DEFAULT 0.00, appraisal_bonus decimal(10,2) DEFAULT 0.00, overtime_pay decimal(10,2) DEFAULT 0.00, subsidy decimal(10,2) DEFAULT 0.00, penalty decimal(10,2) DEFAULT 0.00, income_tax decimal(10,2) DEFAULT 0.00, social_insurance decimal(10,2) DEFAULT 0.00, gross_pay decimal(10,2) DEFAULT 0.00, net_pay decimal(10,2) DEFAULT 0.00, remark varchar(255) DEFAULT , PRIMARY KEY (id), UNIQUE KEY uk_member_month (member_id,year,month), KEY idx_year_month (year,month) ) ENGINEInnoDB DEFAULT CHARSETutf8 COMMENT工资表;参数说明basic_salary用decimal(10,2)而不是float因为金额计算不能有浮点误差。uk_member_month唯一索引保证同一员工同一月份只能有一条工资记录这是防止重复生成工资数据的关键约束。查询月份汇总时优先走idx_year_month比在主键上扫描快得多。2.3 关联查询的两条典型路径实际页面中工资列表很少只查salary表通常要带出员工姓名和部门名称。ThinkPHP 3.x 的链式操作写起来很直观$salaryList M(salary) -alias(s) -join(member m ON s.member_id m.id) -join(department d ON m.department_id d.id) -field(s.*, m.name, m.member_code, d.name AS dept_name) -where(array(s.year 2025, s.month 5)) -order(d.id ASC, m.member_code ASC) -select();这里alias给表起别名join把员工表和部门表连进来where用数组条件避免拼字符串注入。order按部门再按员工编号排序保证导出Excel时顺序一致。一个容易踩的坑是字段名name同时出现在member和department表里所以部门名称必须用AS dept_name转别名否则select()返回的数组里name可能被后一条join覆盖。批量查询用select()返回二维数组查单条记录用find()这两者的返回结构不同前端模板取值方式也要跟着变。3. 登录权限与部门员工管理把EasyUI和ThinkPHP串起来3.1 基于会话的登录态控制后台用的是EasyUI管理界面入口统一走后端控制器。常见做法是在CommonController里写一个初始化方法所有后台控制器继承它class CommonController extends Controller { public function _initialize() { if (!isset($_SESSION[admin_id])) { $this-redirect(Login/index); } // 记录当前登录人信息供后续操作人字段使用 $this-admin_info session(admin_info); } }_initialize是ThinkPHP在每个控制器方法执行前自动调用的钩子。这里判断会话里是否存在admin_id不存在就跳转登录页避免每个方法都重复写鉴权逻辑。登录成功后要重新生成会话ID防止会话固定攻击session_regenerate_id(); $_SESSION[admin_id] $user[id]; $_SESSION[admin_info] $user;3.2 员工列表的分页与JSON输出EasyUI的datagrid组件期望后端返回{total: N, rows: [...]}的结构。前端通过jQuery发异步请求后端按页码和每页条数查询。代码如下public function memberList() { $page I(post.page, 1, intval); $rows I(post.rows, 20, intval); $keyword I(post.keyword, , trim); $where array(); if ($keyword ! ) { $where[name|member_code] array(like, % . $keyword . %); } $total M(member)-where($where)-count(); $list M(member) -where($where) -order(id DESC) -limit(($page - 1) * $rows, $rows) -select(); $this-ajaxReturn(array(total $total, rows $list), JSON); }参数说明I(post.page, 1, intval)是ThinkPHP的输入过滤方法默认值1强制转整型避免把page传成字符串导致SQL拼接异常。where里的name|member_code表示在姓名或员工编号两个字段上做模糊匹配想扩大搜索范围可以继续加字段。limit($offset, $length)是分页计算的核心前端EasyUI会把当前页码和每页条数通过POST传过来。前端对应的EasyUI初始化代码也很关键$(#memberGrid).datagrid({ url: /admin/member/memberList, method: post, pagination: true, pageSize: 20, columns: [[ { field: member_code, title: 员工编号, width: 100 }, { field: name, title: 姓名, width: 80 }, { field: department_id, title: 部门ID, width: 60 } ]] });提示如果后端返回的字段名和前端field对不上表格只会显示空白行优先用浏览器的网络面板检查ajaxReturn输出的原始JSON而不是直接改前端代码。3.3 部门员工新增时的数据校验新增员工时源码里会先查部门是否存在再校验员工编号是否重复最后才插入。这一步能避免外键悬空$dept M(department)-find($department_id); if (!$dept) { $this-error(所选部门不存在); } $exist M(member)-where(array(member_code $member_code))-find(); if ($exist) { $this-error(员工编号已存在); } $member_id M(member)-add($data);add($data)返回自增主键。插入完成后记得把员工密码做哈希处理不要明文入库。ThinkPHP 3.x 可以用md5($password . $salt)新系统建议直接用password_hash()虽然老框架不支持原生调用但可以在模型中封装一个方法。4. 考勤、工资、考核三个核心模块怎么联动4.1 考勤月度汇总的统计逻辑考勤表里存的是汇总结果但数据从哪来常见做法是系统提供“一键生成月度考勤”功能管理员把Excel里的迟到、旷工、请假天数导入或者直接在页面上按员工维护。实现时用事务保证批量写入不产生半截数据$attendanceModel M(attendance); $attendanceModel-startTrans(); try { foreach ($attendanceList as $row) { $data array( member_id $row[member_id], year $year, month $month, present_days $row[present_days], late_days $row[late_days], absent_days $row[absent_days], leave_days $row[leave_days], add_time date(Y-m-d H:i:s) ); $attendanceModel-add($data); } $attendanceModel-commit(); $this-success(考勤数据已生成); } catch (Exception $e) { $attendanceModel-rollback(); $this-error(生成失败 . $e-getMessage()); }startTrans和commit/rollback组合能保证一个月的数据要么全部写入要么全部回滚。否则循环到第10个人时报错前9个人的数据就会造成“部分存在”的脏状态。4.2 工资计算的规则引擎写法工资计算是本系统最有含金量的部分。应发工资 基本工资 全勤奖励 考核奖励 加班工资 津贴补助 - 惩罚金额实发工资 应发工资 - 个人所得税 - 五险一金。源码里的核心逻辑是把公式拆成多个步骤每步在独立方法里完成方便改规则public function calcSalary($member_id, $year, $month) { $member M(member)-find($member_id); $attendance M(attendance) -where(array(member_id$member_id,year$year,month$month)) -find(); $appraisal M(appraisal) -where(array(member_id$member_id,year$year,month$month)) -find(); // 全勤奖励无迟到旷工请假时发放 $attendance_bonus 0; if ($attendance $attendance[late_days] 0 $attendance[absent_days] 0 $attendance[leave_days] 0) { $attendance_bonus 200; } $gross $member[basic_salary] $attendance_bonus $appraisal[appraisal_bonus] $overtime_pay $subsidy - $penalty; $net $gross - $income_tax - $social_insurance; return array(gross $gross, net $net); }这里的策略是“先取数再判断再计算”。全勤判断依赖考勤表考核奖励依赖考核表的结果。如果某员工当月没有考勤记录或考核记录代码要处理$attendance和$appraisal为false的情况否则直接取字段会报错。建议在循环前统一校验缺失记录就跳过并记录日志而不是中断整个批量计算。4.3 考核奖励与工资的联动考核表里有“考核结果”和“考核奖励”两个字段。结果可能是“优秀”“合格”“不合格”对应不同的奖励金额。这里用一个映射表驱动比写一串if else清晰考核结果考核奖励说明优秀500绩效突出合格200达到要求不合格0不发放奖励代码里维护映射数组$appraisal_bonus_map array( 优秀 500, 合格 200, 不合格 0 ); $bonus isset($appraisal_bonus_map[$appraisal[result]]) ? $appraisal_bonus_map[$appraisal[result]] : 0;用isset判断是因为如果考核结果字段里混入了空格或未定义的值直接取下标会抛警告。把奖励规则从业务代码里抽成配置数组或者数据库字典表后续调整金额时不用改代码改数据即可。这也是这个毕业设计里比较实用的设计点。4.4 工资数据的幂等重算工资表有唯一索引uk_member_month所以不能简单用add()插入要改成“存在则更新否则新增”。ThinkPHP里可以这样处理$salaryData array( member_id $member_id, year $year, month $month, gross_pay $gross, net_pay $net ); $existing M(salary)-where( array(member_id$member_id,year$year,month$month) )-find(); if ($existing) { M(salary)-where(array(id$existing[id]))-save($salaryData); } else { M(salary)-add($salaryData); }这样同一个月无论点几次“计算工资”结果都一样不会生成重复记录。保存时把gross_pay和net_pay一起落库查询和导出时不需要再次动态计算。5. ThinkPHP 3.2 源码迁移到 PHP 8 环境的四个必修点这套源码的开发环境比较早直接放到新高版本PHP环境下常出现兼容性问题。以下是我在运行这类旧项目时最常见的四个坑和处理方法。5.1 去掉each()和create_function()PHP 7.2 移除each()PHP 8.0 移除create_function()。如果源码里的模板或插件用到这些函数页面会直接白屏。搜索整个项目代码里的这两个函数名把each($arr)改成foreach ($arr as $key $value)把create_function($a,$b, return $a$b;)改成匿名函数function($a, $b) { return $a $b; }。5.2 数据库连接方式ThinkPHP 3.2 默认用mysql_*系列函数PHP 7.0 开始废弃PHP 8 完全移除。解决方法是在数据库配置里把DB_TYPE设为mysqliTP3.2 的DbMysqli驱动能直接兼容。检查Conf/config.phpDB_TYPE mysqli, DB_HOST 127.0.0.1, DB_NAME company_system, DB_USER root, DB_PWD your_password, DB_PORT 3306, DB_PREFIX tp_,如果原来用的是mysql改成mysqli后大部分查询语句不用动。但如果代码里手写了mysql_real_escape_string之类的函数必须替换为参数绑定或I()过滤。5.3 模板引擎的PHP标签兼容TP3.2的模板里经常写php echo xxx; /phpPHP 8 对短标签支持更严格。建议把php标签内的逻辑移到控制器里模板只保留volist、if这类TP标签。如果迁移工作量大可以在php.ini里确认short_open_tag On但这只是临时缓解新代码不要再写原生PHP短标签。5.4 使用内置服务器快速验证开发阶段不想配置完整站点时可以用PHP内置服务器跑起来php -S localhost:8080 -t .在项目根目录执行后访问http://localhost:8080即可看到登录页。注意TP的URL生成依赖PATH_INFO内置服务器下需要确认路由是否正常如果404就改用Apache或Nginx配合伪静态规则把请求转到入口文件index.php。最后再给一个排查技巧遇到Class PDO not found或Call to undefined function这类错误时先执行php -m查看已加载扩展缺哪个就在php.ini里启用哪个。这套系统的价值不只在于能跑起来更在于你能从它的工资计算和关联查询里看到一套小型业务系统从数据库设计到前后端联调的完整链路照着改一版就能用到自己的项目里。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。