当前位置: 首页 > article >正文

Java全栈项目 - 智能考勤管理系统

项目介绍

智能考勤管理系统是一个基于 Java 全栈技术开发的现代化企业考勤解决方案。该系统采用前后端分离架构,实现了员工考勤、请假管理、统计分析等核心功能,旨在帮助企业提高人力资源管理效率。

技术栈

后端技术

  • Spring Boot 2.6.x
  • Spring Security
  • MyBatis-Plus
  • MySQL 8.0
  • Redis
  • JWT

前端技术

  • Vue 3
  • Element Plus
  • Axios
  • Echarts
  • Vite

核心功能

1. 考勤管理

  • 上下班打卡
  • 加班申请
  • 外出登记
  • 考勤规则设置
  • 异常考勤处理

2. 请假管理

  • 请假申请
  • 请假审批
  • 请假类型配置
  • 假期余额查询

3. 统计分析

  • 考勤统计报表
  • 部门出勤率分析
  • 加班统计
  • 请假趋势分析

4. 系统管理

  • 用户管理
  • 角色权限
  • 部门管理
  • 系统配置

系统架构

├── smart-attendance-system
│   ├── attendance-admin        # 后台管理服务
│   ├── attendance-api         # 接口服务
│   ├── attendance-common      # 公共模块
│   ├── attendance-model       # 数据模型
│   └── attendance-web         # 前端项目

数据库设计

核心表结构

-- 员工表
CREATE TABLE sys_employee (
    id BIGINT PRIMARY KEY,
    emp_no VARCHAR(32),
    name VARCHAR(50),
    department_id BIGINT,
    position VARCHAR(50),
    status TINYINT,
    create_time DATETIME
);

-- 考勤记录表
CREATE TABLE attendance_record (
    id BIGINT PRIMARY KEY,
    emp_id BIGINT,
    check_in_time DATETIME,
    check_out_time DATETIME,
    status TINYINT,
    type TINYINT,
    remark VARCHAR(255)
);

-- 请假记录表
CREATE TABLE leave_record (
    id BIGINT PRIMARY KEY,
    emp_id BIGINT,
    leave_type TINYINT,
    start_time DATETIME,
    end_time DATETIME,
    reason VARCHAR(255),
    status TINYINT
);

项目亮点

1. 人脸识别打卡

  • 集成人脸识别SDK
  • 活体检测
  • 高精度识别算法

2. 智能定位打卡

  • 基于地理围栏技术
  • 支持移动端GPS定位
  • 异地打卡预警

3. 灵活的考勤规则

  • 多班次管理
  • 弹性工时
  • 节假日智能排班
  • 加班规则配置

4. 数据可视化

  • 直观的统计图表
  • 多维度数据分析
  • 自定义报表导出

性能优化

1. 缓存优化

@Cacheable(value = "attendance", key = "#empId")
public AttendanceDTO getAttendanceInfo(Long empId) {
    // 获取考勤信息逻辑
}

2. SQL优化

@Select("SELECT DATE_FORMAT(check_in_time,'%Y-%m-%d') as date, " +
        "COUNT(*) as count FROM attendance_record " +
        "WHERE emp_id = #{empId} " +
        "GROUP BY DATE_FORMAT(check_in_time,'%Y-%m-%d')")
List<StatisticsDTO> getAttendanceStatistics(Long empId);

3. 接口性能

  • 接口响应时间控制在200ms以内
  • 使用线程池处理异步任务
  • 批量操作优化

安全性设计

1. 身份认证

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter(authenticationManager()));
    }
}

2. 数据安全

  • 敏感数据加密
  • SQL注入防护
  • XSS防御

部署方案

1. 容器化部署

version: '3'
services:
  attendance-api:
    image: attendance-api:latest
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod

2. 高可用方案

  • 服务器集群
  • 负载均衡
  • 数据库主从复制

项目总结

智能考勤管理系统采用现代化的技术栈和架构设计,实现了企业考勤管理的智能化和自动化。系统具有良好的可扩展性和维护性,为企业提供了一个高效、可靠的考勤解决方案。

未来展望

  • 引入机器学习算法,实现考勤预测
  • 集成企业微信等第三方平台
  • 开发移动端应用
  • 支持多租户架构

智能考勤系统 - 考勤与请假管理模块详解

一、考勤管理模块

1. 上下班打卡

1.1 打卡记录表设计
CREATE TABLE attendance_record (
    id BIGINT PRIMARY KEY,
    emp_id BIGINT COMMENT '员工ID',
    check_type TINYINT COMMENT '打卡类型:1-上班,2-下班',
    check_time DATETIME COMMENT '打卡时间',
    check_location VARCHAR(255) COMMENT '打卡地点',
    device_info VARCHAR(100) COMMENT '设备信息',
    face_image VARCHAR(255) COMMENT '人脸照片URL',
    status TINYINT COMMENT '状态:1-正常,2-迟到,3-早退',
    remark VARCHAR(255) COMMENT '备注',
    create_time DATETIME,
    FOREIGN KEY (emp_id) REFERENCES sys_employee(id)
);
1.2 打卡服务实现
@Service
@Slf4j
public class CheckInService {
    
    @Autowired
    private AttendanceRuleService ruleService;
    @Autowired
    private FaceRecognitionService faceService;
    @Autowired
    private LocationService locationService;
    
    @Transactional
    public CheckInResult doCheckIn(CheckInDTO checkInDTO) {
        // 1. 人脸识别验证
        boolean faceValid = faceService.verify(
            checkInDTO.getEmpId(), 
            checkInDTO.getFaceImage()
        );
        if (!faceValid) {
            throw new BusinessException("人脸识别失败");
        }
        
        // 2. 位置验证
        boolean locationValid = locationService.verifyLocation(
            checkInDTO.getLocation(),
            checkInDTO.getEmpId()
        );
        if (!locationValid) {
            throw new BusinessException("不在打卡范围内");
        }
        
        // 3. 判断打卡类型和状态
        AttendanceRule rule = ruleService.getEmployeeRule(checkInDTO.getEmpId());
        CheckInStatus status = calculateStatus(
            checkInDTO.getCheckTime(), 
            rule
        );
        
        // 4. 保存打卡记录
        AttendanceRecord record = new AttendanceRecord();
        record.setEmpId(checkInDTO.getEmpId());
        record.setCheckType(checkInDTO.getCheckType());
        record.setCheckTime(checkInDTO.getCheckTime());
        record.setStatus(status.getCode());
        
        attendanceMapper.insert(record);
        
        // 5. 返回打卡结果
        return new CheckInResult(status, record.getId());
    }
    
    private CheckInStatus calculateStatus(LocalDateTime checkTime, 
                                       AttendanceRule rule) {
        LocalTime time = checkTime.toLocalTime();
        
        if (isCheckIn) {
            if (time.isAfter(rule.getLateTime())) {
                return CheckInStatus.LATE;
            }
        } else {
            if (time.isBefore(rule.getEarlyLeaveTime())) {
                return CheckInStatus.EARLY_LEAVE;
            }
        }
        
        return CheckInStatus.NORMAL;
    }
}
1.3 打卡界面实现
<template>
  <div class="check-in-container">
    <div class="camera-container">
      <video ref="video" class="camera-preview"></video>
      <canvas ref="canvas" style="display: none;"></canvas>
    </div>
    
    <div class="check-in-info">
      <div class="time-display">
        {{ currentTime }}
      </div>
      
      <el-button 
        type="primary" 
        size="large" 
        @click="handleCheckIn"
        :loading="checking"
      >
        {{ checkType === 1 ? '上班打卡' : '下班打卡' }}
      </el-button>
      
      <div class="location-info">
        当前位置:{{ location }}
      </div>
    </div>
    
    <!-- 打卡结果弹窗 -->
    <el-dialog
      title="打卡结果"
      :visible.sync="showResult"
      width="300px"
      center
    >
      <div class="check-result">
        <i :class="resultIcon"></i>
        <span>{{ resultMessage }}</span>
        <div class="check-time">{{ checkTime }}</div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  data() {
    return {
      checking: false,
      currentTime: '',
      location: '',
      checkType: this.getCheckType(),
      showResult: false,
      resultMessage: '',
      stream: null
    }
  },
  
  methods: {
    async initCamera() {
      try {
        this.stream = await navigator.mediaDevices.getUserMedia({
          video: true
        });
        this.$refs.video.srcObject = this.stream;
      } catch (error) {
        this.$message.error('摄像头启动失败');
      }
    },
    
    getCheckType() {
      const now = new Date();
      const hour = now.getHours();
      return hour < 12 ? 1 : 2; // 12点前为上班打卡
    },
    
    async handleCheckIn() {
      try {
        this.checking = true;
        
        // 1. 获取人脸图片
        const faceImage = this.captureFace();
        
        // 2. 获取位置信息
        const location = await this.getCurrentLocation();
        
        // 3. 提交打卡
        const result = await this.$api.attendance.checkIn({
          checkType: this.checkType,
          faceImage,
          location,
          checkTime: new Date()
        });
        
        // 4. 显示结果
        this.showCheckResult(result);
        
      } catch (error) {
        this.$message.error(error.message);
      } finally {
        this.checking = false;
      }
    },
    
    captureFace() {
      const canvas = this.$refs.canvas;
      const video = this.$refs.video;
      const context = canvas.getContext('2d');
      
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      context.drawImage(video, 0, 0);
      
      return canvas.toDataURL('image/jpeg');
    }
  },
  
  mounted() {
    this.initCamera();
    // 更新当前时间
    setInterval(() => {
      this.currentTime = new Date().toLocaleTimeString();
    }, 1000);
  },
  
  beforeDestroy() {
    // 关闭摄像头
    if (this.stream) {
      this.stream.getTracks().forEach(track => track.stop());
    }
  }
}
</script>

2. 加班申请

2.1 加班申请表
CREATE TABLE overtime_application (
    id BIGINT PRIMARY KEY,
    emp_id BIGINT,
    start_time DATETIME,
    end_time DATETIME,
    overtime_type TINYINT COMMENT '加班类型:1-工作日,2-周末,3-节假日',
    reason VARCHAR(500),
    status TINYINT COMMENT '状态:0-待审批,1-已通过,2-已拒绝',
    approver_id BIGINT,
    approve_time DATETIME,
    approve_remark VARCHAR(255),
    create_time DATETIME
);
2.2 加班服务实现
@Service
public class OvertimeService {
    
    @Autowired
    private WorkflowService workflowService;
    
    public void applyOvertime(OvertimeDTO overtimeDTO) {
        // 1. 验证加班时长
        validateOvertimeHours(overtimeDTO);
        
        // 2. 创建加班申请
        OvertimeApplication application = new OvertimeApplication();
        BeanUtils.copyProperties(overtimeDTO, application);
        application.setStatus(ApprovalStatus.PENDING.getCode());
        
        overtimeMapper.insert(application);
        
        // 3. 发起工作流
        workflowService.startProcess(
            "overtime_process",
            application.getId(),
            overtimeDTO.getEmpId()
        );
    }
    
    private void validateOvertimeHours(OvertimeDTO overtime) {
        // 计算加班时长
        long hours = ChronoUnit.HOURS.between(
            overtime.getStartTime(), 
            overtime.getEndTime()
        );
        
        // 获取员工当月已加班时长
        int monthlyHours = overtimeMapper.getMonthlyHours(
            overtime.getEmpId(),
            overtime.getStartTime()
        );
        
        // 验证是否超过月度限制
        if (monthlyHours + hours > MAX_MONTHLY_HOURS) {
            throw new BusinessException("超过月度加班时长限制");
        }
    }
}

3. 外出登记

3.1 外出登记表
CREATE TABLE business_trip (
    id BIGINT PRIMARY KEY,
    emp_id BIGINT,
    start_time DATETIME,
    end_time DATETIME,
    destination VARCHAR(255),
    purpose VARCHAR(500),
    status TINYINT,
    contact_info VARCHAR(100),
    create_time DATETIME
);
3.2 外出服务实现
@Service
public class BusinessTripService {
    
    public void register(BusinessTripDTO tripDTO) {
        // 1. 检查是否有重叠的外出记录
        boolean hasOverlap = checkTimeOverlap(
            tripDTO.getEmpId(),
            tripDTO.getStartTime(),
            tripDTO.getEndTime()
        );
        
        if (hasOverlap) {
            throw new BusinessException("当前时间段已有外出记录");
        }
        
        // 2. 保存外出记录
        BusinessTrip trip = new BusinessTrip();
        BeanUtils.copyProperties(tripDTO, trip);
        tripMapper.insert(trip);
        
        // 3. 更新考勤规则
        attendanceRuleService.addException(
            tripDTO.getEmpId(),
            tripDTO.getStartTime(),
            tripDTO.getEndTime(),
            AttendanceExceptionType.BUSINESS_TRIP
        );
    }
}

4. 考勤规则设置

4.1 考勤规则表
CREATE TABLE attendance_rule (
    id BIGINT PRIMARY KEY,
    rule_name VARCHAR(50),
    work_start_time TIME,
    work_end_time TIME,
    late_minutes INT COMMENT '迟到判定分钟数',
    early_leave_minutes INT COMMENT '早退判定分钟数',
    work_hours DECIMAL(4,1) COMMENT '每日工时',
    flexible_time INT COMMENT '弹性工作时间(分钟)',
    dept_id BIGINT COMMENT '适用部门',
    status TINYINT,
    create_time DATETIME
);
4.2 规则配置服务
@Service
public class AttendanceRuleService {
    
    @Cacheable(value = "attendance_rule", key = "#deptId")
    public AttendanceRule getDeptRule(Long deptId) {
        return ruleMapper.selectByDeptId(deptId);
    }
    
    @CacheEvict(value = "attendance_rule", key = "#rule.deptId")
    public void updateRule(AttendanceRule rule) {
        validateRule(rule);
        ruleMapper.updateById(rule);
    }
    
    private void validateRule(AttendanceRule rule) {
        // 验证工作时间设置
        if (rule.getWorkEndTime()
                .isBefore(rule.getWorkStartTime())) {
            throw new BusinessException("下班时间不能早于上班时间");
        }
        
        // 验证弹性工作时间
        if (rule.getFlexibleTime() != null 
            && rule.getFlexibleTime() > MAX_FLEXIBLE_TIME) {
            throw new BusinessException("弹性工作时间超出限制");
        }
    }
}

5. 异常考勤处理

5.1 异常考勤表
CREATE TABLE attendance_exception (
    id BIGINT PRIMARY KEY,
    emp_id BIGINT,
    exception_date DATE,
    exception_type TINYINT COMMENT '异常类型:1-漏打卡,2-迟到,3-早退',
    handle_status TINYINT COMMENT '处理状态',
    handle_result VARCHAR(255),
    handle_time DATETIME,
    handler_id BIGINT,
    create_time DATETIME
);
5.2 异常处理服务
@Service
public class ExceptionHandleService {
    
    @Async
    public void handleException(AttendanceException exception) {
        // 1. 判断异常类型
        switch (exception.getExceptionType()) {
            case MISSING_CHECK:
                handleMissingCheck(exception);
                break;
            case LATE:
                handleLate(exception);
                break;
            case EARLY_LEAVE:
                handleEarlyLeave(exception);
                break;
        }
        
        // 2. 发送通知
        notificationService.sendExceptionNotice(exception);
        
        // 3. 更新处理状态
        exceptionMapper.updateStatus(
            exception.getId(),
            ExceptionStatus.HANDLED
        );
    }
    
    private void handleMissingCheck(AttendanceException exception) {
        // 检查是否有相关的请假或外出记录
        List<LeaveRecord> leaveRecords = leaveMapper
            .findByEmpAndDate(
                exception.getEmpId(), 
                exception.getExceptionDate()
            );
            
        if (!leaveRecords.isEmpty()) {
            // 有请假记录,标记为已确认
            exception.setHandleResult("已确认请假");
            return;
        }
        
        // 发起补卡申请
        createSupplementaryApplication(exception);
    }
}

二、请假管理模块

1. 请假申请

1.1 请假记录表
CREATE TABLE leave_record (
    id BIGINT PRIMARY KEY,
    emp_id BIGINT,
    leave_type TINYINT COMMENT '请假类型',
    start_time DATETIME,
    end_time DATETIME,
    duration DECIMAL(5,1) COMMENT '请假时长(天)',
    reason VARCHAR(500),
    status TINYINT COMMENT '状态:0-待审批,1-已通过,2-已拒绝',
    approver_id BIGINT,
    approve_time DATETIME,
    approve_remark VARCHAR(255),
    attachment_url VARCHAR(255) COMMENT '附件URL',
    create_time DATETIME
);
1.2 请假服务实现
@Service
public class LeaveService {
    
    @Autowired
    private LeaveBalanceService balanceService;
    @Autowired
    private WorkflowService workflowService;
    
    @Transactional
    public void applyLeave(LeaveApplicationDTO leaveDTO) {
        // 1. 校验请假时长
        validateLeaveDuration(leaveDTO);
        
        // 2. 检查假期余额
        checkLeaveBalance(
            leaveDTO.getEmpId(),
            leaveDTO.getLeaveType(),
            leaveDTO.getDuration()
        );
        
        // 3. 创建请假记录
        LeaveRecord record = new LeaveRecord();
        BeanUtils.copyProperties(leaveDTO, record);
        record.setStatus(ApprovalStatus.PENDING.getCode());
        
        leaveMapper.insert(record);
        
        // 4. 发起工作流
        workflowService.startProcess(
            "leave_process",
            record.getId(),
            leaveDTO.getEmpId()
        );
    }
    
    private void checkLeaveBalance(Long empId, 
                                 LeaveType leaveType, 
                                 BigDecimal duration) {
        LeaveBalance balance = balanceService
            .getBalance(empId, leaveType);
            
        if (balance.getRemaining().compareTo(duration) < 0) {
            throw new BusinessException(
                leaveType.getName() + "假期余额不足"
            );
        }
    }
}

2. 请假审批

2.1 审批流程配置
@Configuration
public class LeaveWorkflowConfig {
    
    @Autowired
    private RuntimeService runtimeService;
    
    @Bean
    public ProcessEngineConfiguration leaveProcess() {
        return ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration()
            .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)
            .setJdbcUrl("jdbc:mysql://localhost:3306/workflow")
            .setJdbcDriver("com.mysql.cj.jdbc.Driver")
            .setJdbcUsername("root")
            .setJdbcPassword("password")
            .setActivityFontName("宋体")
            .setLabelFontName("宋体")
            .setAnnotationFontName("宋体");
    }
}
2.2 审批服务实现
@Service
public class LeaveApprovalService {
    
    @Autowired
    private TaskService taskService;
    @Autowired
    private LeaveBalanceService balanceService;
    
    @Transactional
    public void approve(ApprovalDTO approvalDTO) {
        // 1. 获取请假记录
        LeaveRecord record = leaveMapper
            .selectById(approvalDTO.getRecordId());
            
        if (record == null) {
            throw new BusinessException("请假记录不存在");
        }
        
        // 2. 更新审批状态
        record.setStatus(approvalDTO.getApproved() 
            ? ApprovalStatus.APPROVED.getCode()
            : ApprovalStatus.REJECTED.getCode());
            
        record.setApproveRemark(approvalDTO.getRemark());
        record.setApproveTime(LocalDateTime.now());
        record.setApproverId(approvalDTO.getApproverId());
        
        leaveMapper.updateById(record);
        
        // 3. 如果审批通过,扣减假期余额
        if (approvalDTO.getApproved()) {
            balanceService.deductBalance(
                record.getEmpId(),
                record.getLeaveType(),
                record.getDuration()
            );
        }
        
        // 4. 完成工作流任务
        taskService.complete(approvalDTO.getTaskId());
        
        // 5. 发送通知
        notificationService.sendApprovalResult(record);
    }
}

3. 请假类型配置

3.1 请假类型表
CREATE TABLE leave_type (
    id BIGINT PRIMARY KEY,
    type_name VARCHAR(50),
    type_code VARCHAR(50),
    paid TINYINT COMMENT '是否带薪',
    max_duration INT COMMENT '最大请假天数',
    min_unit DECIMAL(2,1) COMMENT '最小请假单位(天)',
    need_attachment TINYINT COMMENT '是否需要附件',
    status TINYINT,
    create_time DATETIME
);
3.2 类型配置服务
@Service
public class LeaveTypeService {
    
    @Cacheable(value = "leave_type", key = "#typeId")
    public LeaveType getLeaveType(Long typeId) {
        return typeMapper.selectById(typeId);
    }
    
    @CacheEvict(value = "leave_type", allEntries = true)
    public void updateLeaveType(LeaveType leaveType) {
        validateLeaveType(leaveType);
        typeMapper.updateById(leaveType);
    }
    
    private void validateLeaveType(LeaveType type) {
        // 验证最大请假天数
        if (type.getMaxDuration() <= 0) {
            throw new BusinessException("最大请假天数必须大于0");
        }
        
        // 验证最小请假单位
        if (type.getMinUnit().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException("最小请假单位必须大于0");
        }
    }
}

4. 假期余额查询

4.1 假期余额表
CREATE TABLE leave_balance (
    id BIGINT PRIMARY KEY,
    emp_id BIGINT,
    leave_type TINYINT,
    year INT,
    total_days DECIMAL(5,1),
    used_days DECIMAL(5,1),
    remaining_days DECIMAL(5,1),
    update_time DATETIME
);
4.2 余额查询服务
@Service
public class LeaveBalanceService {
    
    @Cacheable(value = "leave_balance", 
               key = "#empId + ':' + #leaveType")
    public LeaveBalance getBalance(Long empId, LeaveType leaveType) {
        return balanceMapper.selectByEmpAndType(empId, leaveType);
    }
    
    @Transactional
    @CacheEvict(value = "leave_balance", 
                key = "#empId + ':' + #leaveType")
    public void deductBalance(Long empId, 
                            LeaveType leaveType, 
                            BigDecimal days) {
        LeaveBalance balance = getBalance(empId, leaveType);
        
        if (balance == null) {
            throw new BusinessException("假期余额记录不存在");
        }
        
        // 检查余额是否足够
        if (balance.getRemainingDays().compareTo(days) < 0) {
            throw new BusinessException("假期余额不足");
        }
        
        // 更新已使用和剩余天数
        balance.setUsedDays(
            balance.getUsedDays().add(days)
        );
        balance.setRemainingDays(
            balance.getRemainingDays().subtract(days)
        );
        
        balanceMapper.updateById(balance);
    }
    
    // 年度假期初始化
    @Scheduled(cron = "0 0 0 1 1 ?")  // 每年1月1日执行
    public void initializeYearlyBalance() {
        int year = LocalDate.now().getYear();
        
        // 获取所有在职员工
        List<Employee> employees = employeeMapper
            .selectAllActive();
            
        for (Employee emp : employees) {
            // 初始化各类假期余额
            initializeBalance(emp.getId(), year);
        }
    }
}
4.3 余额查询界面
<template>
  <div class="leave-balance">
    <el-card class="balance-card">
      <div slot="header">
        <span>假期余额</span>
        <el-button 
          type="text" 
          @click="refreshBalance"
          style="float: right;"
        >
          刷新
        </el-button>
      </div>
      
      <el-table :data="balanceList" border>
        <el-table-column 
          prop="typeName" 
          label="假期类型"
        />
        <el-table-column 
          prop="totalDays" 
          label="总天数"
        />
        <el-table-column 
          prop="usedDays" 
          label="已用天数"
        />
        <el-table-column 
          prop="remainingDays" 
          label="剩余天数"
        >
          <template slot-scope="scope">
            <span :class="{
              'warning': scope.row.remainingDays < 5,
              'danger': scope.row.remainingDays <= 0
            }">
              {{ scope.row.remainingDays }}
            </span>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    
    <!-- 假期使用记录 -->
    <el-card class="usage-card">
      <div slot="header">
        <span>假期使用记录</span>
      </div>
      
      <el-table :data="usageRecords" border>
        <el-table-column 
          prop="leaveType" 
          label="假期类型"
        />
        <el-table-column 
          prop="startTime" 
          label="开始时间"
        />
        <el-table-column 
          prop="endTime" 
          label="结束时间"
        />
        <el-table-column 
          prop="duration" 
          label="请假天数"
        />
        <el-table-column 
          prop="status" 
          label="状态"
        >
          <template slot-scope="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ getStatusText(scope.row.status) }}
            </el-tag>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>

<script>
export default {
  data() {
    return {
      balanceList: [],
      usageRecords: []
    }
  },
  
  methods: {
    async refreshBalance() {
      try {
        const response = await this.$api.leave
          .getBalance(this.userId);
        this.balanceList = response.data;
      } catch (error) {
        this.$message.error('获取假期余额失败');
      }
    },
    
    getStatusType(status) {
      const typeMap = {
        0: 'info',
        1: 'success',
        2: 'danger'
      };
      return typeMap[status] || 'info';
    },
    
    getStatusText(status) {
      const textMap = {
        0: '待审批',
        1: '已通过',
        2: '已拒绝'
      };
      return textMap[status] || '未知';
    }
  },
  
  created() {
    this.refreshBalance();
  }
}
</script>

<style scoped>
.leave-balance {
  padding: 20px;
}

.balance-card {
  margin-bottom: 20px;
}

.warning {
  color: #E6A23C;
}

.danger {
  color: #F56C6C;
}
</style>

这些模块的实现涵盖了:

  • 数据库表设计
  • 业务逻辑实现
  • 工作流集成
  • 缓存管理
  • 定时任务
  • 前端界面开发
  • 权限控制
  • 数据验证

通过这些功能的实现,可以为企业提供完整的考勤和请假管理解决方案。

智能考勤系统 - 统计分析与系统管理模块详解

一、统计分析模块

1. 考勤统计报表

1.1 数据维度
-- 考勤统计视图
CREATE VIEW v_attendance_statistics AS
SELECT 
    e.department_id,
    e.emp_no,
    e.name,
    DATE_FORMAT(ar.check_in_time, '%Y-%m') as month,
    COUNT(DISTINCT DATE(ar.check_in_time)) as work_days,
    COUNT(CASE WHEN ar.status = 1 THEN 1 END) as normal_days,
    COUNT(CASE WHEN ar.status = 2 THEN 1 END) as late_days,
    COUNT(CASE WHEN ar.status = 3 THEN 1 END) as early_leave_days,
    COUNT(CASE WHEN ar.status = 4 THEN 1 END) as absent_days
FROM sys_employee e
LEFT JOIN attendance_record ar ON e.id = ar.emp_id
GROUP BY e.id, DATE_FORMAT(ar.check_in_time, '%Y-%m');
1.2 报表类型
  • 月度考勤汇总表
  • 个人考勤明细表
  • 部门考勤对比表
  • 异常考勤分析表
1.3 数据展示
@Service
public class AttendanceReportService {
    
    @Autowired
    private AttendanceMapper attendanceMapper;
    
    public List<AttendanceReportDTO> generateMonthlyReport(String month) {
        return attendanceMapper.getMonthlyStatistics(month);
    }
    
    // 支持多种导出格式
    public void exportReport(String format, String month) {
        List<AttendanceReportDTO> data = generateMonthlyReport(month);
        switch(format.toLowerCase()) {
            case "excel":
                exportToExcel(data);
                break;
            case "pdf":
                exportToPDF(data);
                break;
            case "csv":
                exportToCSV(data);
                break;
        }
    }
}

2. 部门出勤率分析

2.1 核心指标
  • 部门整体出勤率
  • 迟到率
  • 早退率
  • 缺勤率
  • 加班时长
2.2 可视化展示
<template>
  <div class="department-analysis">
    <!-- 部门出勤率图表 -->
    <div class="chart-container">
      <v-chart :option="chartOption" />
    </div>
    
    <!-- 部门排名列表 -->
    <el-table :data="departmentRanking">
      <el-table-column prop="deptName" label="部门" />
      <el-table-column prop="attendanceRate" label="出勤率">
        <template #default="scope">
          <el-progress 
            :percentage="scope.row.attendanceRate" 
            :color="getColorByRate(scope.row.attendanceRate)"
          />
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      chartOption: {
        title: { text: '部门出勤率分析' },
        series: [{
          type: 'pie',
          data: [
            { value: 95, name: '研发部' },
            { value: 92, name: '市场部' },
            { value: 88, name: '运营部' }
          ]
        }]
      }
    }
  }
}
</script>

3. 加班统计

3.1 加班类型统计
public enum OvertimeType {
    WORKDAY(1, "工作日加班"),
    WEEKEND(2, "周末加班"),
    HOLIDAY(3, "节假日加班");
    
    private int code;
    private String desc;
}

@Service
public class OvertimeStatisticsService {
    
    public Map<String, Object> calculateOvertimeHours(Long empId, String month) {
        Map<String, Object> statistics = new HashMap<>();
        
        // 计算不同类型加班时长
        statistics.put("workdayHours", calculateByType(empId, month, OvertimeType.WORKDAY));
        statistics.put("weekendHours", calculateByType(empId, month, OvertimeType.WEEKEND));
        statistics.put("holidayHours", calculateByType(empId, month, OvertimeType.HOLIDAY));
        
        // 计算加班费
        statistics.put("overtimePay", calculateOvertimePay(statistics));
        
        return statistics;
    }
}

4. 请假趋势分析

4.1 请假类型分布
SELECT 
    leave_type,
    COUNT(*) as count,
    SUM(TIMESTAMPDIFF(HOUR, start_time, end_time)) as total_hours
FROM leave_record
WHERE DATE_FORMAT(start_time, '%Y-%m') = #{month}
GROUP BY leave_type;
4.2 趋势图表配置
const leaveAnalysisChart = {
  xAxis: {
    type: 'category',
    data: ['1月', '2月', '3月', '4月', '5月', '6月']
  },
  yAxis: {
    type: 'value'
  },
  series: [
    {
      name: '病假',
      type: 'line',
      data: [10, 8, 12, 6, 9, 7]
    },
    {
      name: '事假',
      type: 'line',
      data: [5, 7, 4, 8, 6, 9]
    },
    {
      name: '年假',
      type: 'line',
      data: [2, 4, 6, 8, 3, 5]
    }
  ]
}

二、系统管理模块

1. 用户管理

1.1 用户表设计
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    real_name VARCHAR(50),
    email VARCHAR(100),
    mobile VARCHAR(20),
    status TINYINT DEFAULT 1,
    create_time DATETIME,
    update_time DATETIME,
    last_login_time DATETIME,
    remark VARCHAR(255)
);
1.2 用户服务实现
@Service
public class UserService {
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    public void createUser(UserDTO userDTO) {
        // 密码加密
        String encodedPassword = passwordEncoder.encode(userDTO.getPassword());
        
        SysUser user = new SysUser();
        user.setUsername(userDTO.getUsername());
        user.setPassword(encodedPassword);
        // ... 设置其他属性
        
        userMapper.insert(user);
    }
    
    public void updateUserStatus(Long userId, Integer status) {
        userMapper.updateStatus(userId, status);
    }
}

2. 角色权限

2.1 RBAC权限模型
-- 角色表
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY,
    role_name VARCHAR(50),
    role_code VARCHAR(50),
    status TINYINT,
    create_time DATETIME
);

-- 权限表
CREATE TABLE sys_permission (
    id BIGINT PRIMARY KEY,
    parent_id BIGINT,
    name VARCHAR(50),
    type TINYINT,
    permission_code VARCHAR(50),
    path VARCHAR(200),
    component VARCHAR(100),
    icon VARCHAR(50),
    sort INT
);

-- 角色权限关联表
CREATE TABLE sys_role_permission (
    role_id BIGINT,
    permission_id BIGINT,
    PRIMARY KEY(role_id, permission_id)
);
2.2 权限控制实现
@PreAuthorize("hasRole('ADMIN') or hasPermission(#id, 'attendance:view')")
@GetMapping("/attendance/{id}")
public AttendanceDTO getAttendanceDetail(@PathVariable Long id) {
    return attendanceService.getById(id);
}

@Service
public class CustomPermissionEvaluator implements PermissionEvaluator {
    
    @Override
    public boolean hasPermission(Authentication authentication, 
                               Object targetDomainObject, 
                               Object permission) {
        // 权限判断逻辑
        return checkPermission(authentication, permission.toString());
    }
}

3. 部门管理

3.1 部门树形结构
@Data
public class DepartmentTree {
    private Long id;
    private String name;
    private Long parentId;
    private List<DepartmentTree> children;
    private String leader;
    private Integer employeeCount;
}

@Service
public class DepartmentService {
    
    public List<DepartmentTree> buildDepartmentTree() {
        List<Department> allDepts = departmentMapper.selectAll();
        return buildTree(allDepts, 0L);
    }
    
    private List<DepartmentTree> buildTree(List<Department> depts, Long parentId) {
        return depts.stream()
            .filter(dept -> dept.getParentId().equals(parentId))
            .map(dept -> {
                DepartmentTree node = new DepartmentTree();
                node.setId(dept.getId());
                node.setName(dept.getName());
                node.setChildren(buildTree(depts, dept.getId()));
                return node;
            })
            .collect(Collectors.toList());
    }
}

4. 系统配置

4.1 配置管理
@Configuration
@ConfigurationProperties(prefix = "system")
@Data
public class SystemConfig {
    private AttendanceConfig attendance;
    private SecurityConfig security;
    private NotificationConfig notification;
    
    @Data
    public static class AttendanceConfig {
        private String workStartTime;
        private String workEndTime;
        private Integer lateMinutes;
        private Integer earlyLeaveMinutes;
    }
}
4.2 动态配置实现
@Service
public class ConfigService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public void updateConfig(String key, String value) {
        // 更新数据库
        configMapper.updateValue(key, value);
        // 更新缓存
        redisTemplate.opsForValue().set(
            "system:config:" + key, 
            value, 
            1, 
            TimeUnit.DAYS
        );
    }
    
    public String getConfig(String key) {
        // 先从缓存获取
        String value = redisTemplate.opsForValue().get("system:config:" + key);
        if (value == null) {
            // 缓存未命中,从数据库获取
            value = configMapper.selectByKey(key);
            if (value != null) {
                redisTemplate.opsForValue().set(
                    "system:config:" + key, 
                    value, 
                    1, 
                    TimeUnit.DAYS
                );
            }
        }
        return value;
    }
}
4.3 配置页面
<template>
  <div class="system-config">
    <el-form :model="configForm" label-width="120px">
      <el-tabs v-model="activeTab">
        <!-- 考勤配置 -->
        <el-tab-pane label="考勤配置" name="attendance">
          <el-form-item label="上班时间">
            <el-time-picker v-model="configForm.workStartTime" />
          </el-form-item>
          <el-form-item label="下班时间">
            <el-time-picker v-model="configForm.workEndTime" />
          </el-form-item>
        </el-tab-pane>
        
        <!-- 系统配置 -->
        <el-tab-pane label="系统配置" name="system">
          <el-form-item label="系统名称">
            <el-input v-model="configForm.systemName" />
          </el-form-item>
          <el-form-item label="Logo">
            <el-upload
              action="/api/system/upload"
              :show-file-list="false"
              :on-success="handleLogoSuccess">
              <img v-if="configForm.logo" :src="configForm.logo" class="logo">
              <el-button v-else>上传Logo</el-button>
            </el-upload>
          </el-form-item>
        </el-tab-pane>
      </el-tabs>
      
      <el-form-item>
        <el-button type="primary" @click="saveConfig">保存配置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeTab: 'attendance',
      configForm: {
        workStartTime: '',
        workEndTime: '',
        systemName: '',
        logo: ''
      }
    }
  },
  methods: {
    async saveConfig() {
      try {
        await this.$api.system.updateConfig(this.configForm)
        this.$message.success('配置保存成功')
      } catch (error) {
        this.$message.error('配置保存失败')
      }
    }
  }
}
</script>

这些模块的实现涉及到了前后端的完整开发流程,包括:

  • 数据库表设计
  • 后端服务实现
  • 权限控制
  • 缓存优化
  • 前端界面开发
  • 数据可视化

通过这些功能的实现,可以为企业提供完整的考勤管理解决方案。


http://www.kler.cn/a/452834.html

相关文章:

  • Apriori关联规则算法 HNUST【数据分析技术】(2025)
  • PetaLinux 内核输出信息的获取方式
  • 一文详解“二叉树中的深搜“在算法中的应用
  • 《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》简介
  • 【Java集合面试题001】Java中有哪些集合类?请简单介绍
  • axios 常见的content-type、responseType有哪些?
  • 3090. 每个字符最多出现两次的最长子字符串
  • sentinel限流+其他
  • 基于ISO 21434的汽车网络安全实践
  • LRU 缓存
  • 【Apache Paimon】-- 11 -- Flink 消费 kakfa 写 S3 File
  • 全局JDK环境和ES自带的JDK混用导致的ES集群创建失败
  • Spring Boot 知识要点全解析
  • 04.HTTPS的实现原理-HTTPS的混合加密流程
  • 【魅力golang】之-玩转协程
  • Qt之QML应用程序开发:给应用程序添加图标文件
  • 【FastAPI】日志
  • element ui--下拉根据拼音首字母过滤
  • 纯真社区版IP库CZDB数据格式使用教程
  • 05.HTTPS的实现原理-HTTPS的握手流程(TLS1.2)