本文目录导读:

这是一个相对复杂但很常见的需求,要用PHP实现一个考勤报表系统,核心在于数据采集、规则计算和数据展示三个环节。
以下是一个从设计思路到具体实现的分步指南,包含核心代码片段。
整体架构设计
- 数据源:通常是打卡机(硬件)生成的文本/log文件,或者通过API获取,最常见的是存入MySQL数据库。
- 核心表设计:
- 考勤记录表(
attendance_log):存储原始打卡数据。id,user_id,clock_in(上班打卡),clock_out(下班打卡),date(日期)
- 用户/员工表(
users):id,name,department,work_start_time(上班时间),work_end_time(下班时间) - 考勤汇总表(
attendance_summary):存储计算后的结果(可选,用于报表加速)。id,user_id,date,status(正常/迟到/早退/旷工),work_hours,late_minutes,early_minutes
- 假期/异常表(
attendance_exception):请假、出差、外勤记录。
- 考勤记录表(
核心逻辑实现(PHP代码)
数据导入与预处理
假设数据来自打卡机文件或表单提交,你需要将其插入attendance_log。
<?php
// 假设接收到来自打卡机的数据
function importAttendanceData($userId, $timestamp) {
$date = date('Y-m-d', $timestamp);
$time = date('H:i:s', $timestamp);
// 1. 判断是上班打卡还是下班打卡(根据时间或设备号)
// 这里简化处理:每天最早的一条是上班,最晚的一条是下班
$existing = getTodayLog($userId, $date);
if (empty($existing)) {
// 第一条记录视为上班
saveLog($userId, $date, $time, null);
} else {
// 更新下班时间为当前时间(如果当前时间更晚)
if ($time > $existing['clock_in']) {
updateLogClockOut($userId, $date, $time);
}
}
}
// 伪代码: 连接数据库
function saveLog($userId, $date, $clockIn, $clockOut) {
$pdo = new PDO('mysql:host=localhost;dbname=attendance', 'root', '');
$sql = "INSERT INTO attendance_log (user_id, date, clock_in, clock_out) VALUES (?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$userId, $date, $clockIn, $clockOut]);
}
?>
考勤规则计算引擎
这是最复杂的一步,你需要根据公司的考勤制度写逻辑。
<?php
function calculateAttendance($userId, $date) {
// 1. 获取员工信息及当天的打卡记录
$user = getUser($userId);
$log = getAttendanceLog($userId, $date);
$holiday = isHoliday($date); // 检查是否是周末或法定假日
$result = [];
$result['date'] = $date;
$result['user_name'] = $user['name'];
// 2. 判断是否请假
if (hasLeave($userId, $date)) {
$result['status'] = '请假';
$result['work_hours'] = 0;
return $result;
}
// 3. 判断是否无打卡(旷工或休息)
if (empty($log['clock_in']) || empty($log['clock_out'])) {
if ($holiday) {
$result['status'] = '休息';
} else {
$result['status'] = '缺卡/旷工';
}
$result['work_hours'] = 0;
return $result;
}
// 4. 计算迟到 (假设上班时间为 09:00)
$clockInTime = strtotime($log['clock_in']);
$workStartTime = strtotime($user['work_start_time']); // 09:00:00
$lateMinutes = 0;
if ($clockInTime > $workStartTime) {
// 可以设置宽限期,比如9:05前不算迟到
$gracePeriod = 10 * 60; // 10分钟
if ($clockInTime - $workStartTime > $gracePeriod) {
$lateMinutes = round(($clockInTime - $workStartTime) / 60);
}
}
// 5. 计算早退 (假设下班时间为 18:00)
$clockOutTime = strtotime($log['clock_out']);
$workEndTime = strtotime($user['work_end_time']); // 18:00:00
$earlyMinutes = 0;
if ($clockOutTime < $workEndTime) {
$earlyMinutes = round(($workEndTime - $clockOutTime) / 60);
}
// 6. 计算实际工作时长(扣除午休时间,比如12:00-13:00)
$workHours = ($clockOutTime - $clockInTime) / 3600; // 小时
// 扣除午休1小时(如果跨越了中午)
if (strtotime($log['clock_in']) < strtotime('12:00:00') && strtotime($log['clock_out']) > strtotime('13:00:00')) {
$workHours -= 1;
}
// 7. 判定最终状态
if ($lateMinutes > 0 && $earlyMinutes > 0) {
$result['status'] = '迟到且早退';
} elseif ($lateMinutes > 0) {
$result['status'] = '迟到';
} elseif ($earlyMinutes > 0) {
$result['status'] = '早退';
} elseif ($workHours >= 8) { // 假设标准8小时
$result['status'] = '正常';
} else {
$result['status'] = '工时不足';
}
$result['clock_in'] = $log['clock_in'];
$result['clock_out'] = $log['clock_out'];
$result['late_minutes'] = $lateMinutes;
$result['early_minutes'] = $earlyMinutes;
$result['work_hours'] = round($workHours, 2);
// 8. 存储计算结果到汇总表(可选)
saveSummary($userId, $date, $result);
return $result;
}
?>
报表生成与展示
思路:按月查询,对每天的数据应用上述规则,然后汇总。
<?php
// 生成月度考勤报表
function generateMonthlyReport($userId, $yearMonth) {
$startDate = $yearMonth . '-01';
$endDate = date('Y-m-t', strtotime($startDate));
$report = [];
$totalLate = 0;
$totalEarly = 0;
$totalWorkHours = 0;
$abnormalDays = 0;
$currentDate = $startDate;
while ($currentDate <= $endDate) {
// 计算每一天的考勤
$dailyResult = calculateAttendance($userId, $currentDate);
// 汇总数据
if ($dailyResult['status'] !== '休息' && $dailyResult['status'] !== '请假') {
$totalLate += $dailyResult['late_minutes'];
$totalEarly += $dailyResult['early_minutes'];
$totalWorkHours += $dailyResult['work_hours'];
}
if ($dailyResult['status'] !== '正常' && $dailyResult['status'] !== '休息') {
$abnormalDays++;
}
$report['detail'][] = $dailyResult;
$currentDate = date('Y-m-d', strtotime($currentDate . ' +1 day'));
}
// 报表汇总
$report['summary'] = [
'total_work_days' => count(array_filter($report['detail'], fn($d) => $d['status'] !== '休息')),
'total_late_minutes' => $totalLate,
'total_early_minutes' => $totalEarly,
'total_work_hours' => round($totalWorkHours, 2),
'abnormal_days' => $abnormalDays,
'late_days' => count(array_filter($report['detail'], fn($d) => $d['status'] === '迟到'))
];
return $report;
}
// 在页面上展示
$reportData = generateMonthlyReport(1, '2024-01');
?>
数据库查询优化(SQL)
如果你需要直接在数据库层面做简单报表(不做复杂规则),可以使用SQL:
-- 按天汇总每个人的考勤情况(假设上下班时间固定为09:00和18:00)
SELECT
u.name,
a.date,
a.clock_in,
a.clock_out,
CASE
WHEN a.clock_in > '09:00:00' THEN '迟到'
WHEN a.clock_out < '18:00:00' THEN '早退'
WHEN a.clock_in IS NULL THEN '缺勤'
ELSE '正常'
END AS status,
TIMESTAMPDIFF(HOUR, a.clock_in, a.clock_out) - 1 AS work_hours -- 扣除午休1小时
FROM
attendance_log a
JOIN
users u ON a.user_id = u.id
WHERE
a.date BETWEEN '2024-01-01' AND '2024-01-31'
ORDER BY
a.date, u.name;
前端展示与导出
- HTML表格:使用Bootstrap生成漂亮表格。
- 导出Excel:最常用的方案是使用 PhpSpreadsheet 库。
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
// 将 $reportData 导出为Excel
function exportToExcel($reportData, $filename) {
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// 设置表头
$sheet->setCellValue('A1', '日期');
$sheet->setCellValue('B1', '姓名');
$sheet->setCellValue('C1', '上班打卡');
$sheet->setCellValue('D1', '下班打卡');
// ... 其他列
// 填充数据
$row = 2;
foreach ($reportData['detail'] as $item) {
$sheet->setCellValue('A'.$row, $item['date']);
$sheet->setCellValue('B'.$row, $item['user_name']);
$sheet->setCellValue('C'.$row, $item['clock_in']);
$sheet->setCellValue('D'.$row, $item['clock_out']);
$row++;
}
// 生成文件
$writer = new Xlsx($spreadsheet);
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename="'.$filename.'.xlsx"');
$writer->save('php://output');
}
实际项目中的难点与建议
- 复杂排班:不是所有人都是9-6,需要支持 排班表,每个人每天可能不同班次。
- 方案:建立
shifts表(早班、晚班、中班),user_schedule表每天关联一个班次。
- 方案:建立
- 跨天打卡:比如晚班20:00打卡,第二天早8:00下班。
- 方案:
attendance_log应该记录clock_in_time和clock_out_time为完整时间戳(如2024-01-01 20:00:00),不要只存时间。
- 方案:
- 实时性:如果报表需要实时生成,规则计算会很慢。
- 方案:使用 定时任务(Cron Job) 每天凌晨计算前一天的所有数据,存储到汇总表,报表直接从汇总表读取。
- 性能:上万人的公司,每天遍历31天逐行计算会很慢。
- 方案:尽量用SQL窗口函数或存储过程处理;PHP端使用批处理和缓存。
总结实现流程
- 定义数据模型:用户、打卡记录、排班、假期。
- 接收打卡数据:API或文件导入。
- 运行计算脚本:每晚通过Cron执行
php calculate_attendance.php。 - 提供查询接口:根据日期、部门、员工查询
attendance_summary表。 - 导出报表:使用PhpSpreadsheet导出Excel。
如果你需要具体的某个环节(如Excel导出、复杂排班逻辑、SQL优化)的详细代码,可以进一步描述你的需求。