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

news2024/12/26 7:00:28

项目介绍

智能考勤管理系统是一个基于 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.coloradmin.cn/o/2265697.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机毕业设计Python+卷积神经网络租房推荐系统 租房大屏可视化 租房爬虫 hadoop spark 58同城租房爬虫 房源推荐系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

攻防世界 - Web - Level 1 unseping

关注这个靶场的其它相关笔记&#xff1a;攻防世界&#xff08;XCTF&#xff09; —— 靶场笔记合集-CSDN博客 0x01&#xff1a;Write UP 本关是一个 PHP 代码审计关卡&#xff0c;考察的是 PHP 反序列化漏洞以及命令执行的一些绕过手段&#xff0c;下面笔者将带你一步步过关。…

黑马程序员JavaWeb开发教程(前端部分) ---笔记分享

总结 此篇文章记录的内容是不全的&#xff0c;我觉得基础的部分没有记录&#xff0c;我想主要学的是此课程的后端部分&#xff0c;前端部分学校有学习过&#xff0c;我就开倍速一带而过啦&#xff0c;还有就是学校学的是Vue3和此视频讲的Vue2还是有一定区别的。希望能对大家有…

【统计的思想】统计抽样测试(二)

在统计抽样测试里&#xff0c;一旦我们选定了某个测试方案(n|Ac)&#xff0c;我们就可以算出任意不合格品率p对应的接收概率L(p)。把各种可能的p值对应的L(p)连成一条曲线&#xff0c;这就是测试方案(n|Ac)的操作特性曲线。比如&#xff0c;方案(80|1)的操作特性曲线长这个样子…

Pytorch | 利用I-FGSSM针对CIFAR10上的ResNet分类器进行对抗攻击

Pytorch | 利用I-FGSSM针对CIFAR10上的ResNet分类器进行对抗攻击 CIFAR数据集I-FGSSM介绍I-FGSSM代码实现I-FGSSM算法实现攻击效果 代码汇总ifgssm.pytrain.pyadvtest.py 之前已经针对CIFAR10训练了多种分类器&#xff1a; Pytorch | 从零构建AlexNet对CIFAR10进行分类 Pytorch…

【多维DP】力扣576. 出界的路径数

给你一个大小为 m x n 的网格和一个球。球的起始坐标为 [startRow, startColumn] 。你可以将球移到在四个方向上相邻的单元格内&#xff08;可以穿过网格边界到达网格之外&#xff09;。你 最多 可以移动 maxMove 次球。 给你五个整数 m、n、maxMove、startRow 以及 startColu…

react防止页面崩溃

在 React 中&#xff0c;ErrorBoundary 组件是一种用于捕获并处理其子组件树中发生的 JavaScript 错误的机制。它可以帮助你在应用程序中实现优雅的错误处理&#xff0c;防止整个应用崩溃&#xff0c;并为用户提供友好的错误提示。ErrorBoundary 通过使用 React 的生命周期方法…

Python使用requests_html库爬取掌阅书籍(附完整源码及使用说明)

教程概述 本教程先是幽络源初步教学分析掌阅书籍的网络结构&#xff0c;最后提供完整的爬取源码与使用说明&#xff0c;并展示结果&#xff0c;切记勿将本教程内容肆意非法使用。 原文链接&#xff1a;Python使用requests_html库爬取掌阅书籍&#xff08;附完整源码及使用说明…

基于earthSDK三维地图组件开发

上效果 功能点 测量分析相机位置切换geojson数据加载地图打点&#xff0c;显示信息点击回传数据二三位切换 这里二三维切换通上篇openlayers分享&#xff0c;技术交流V:bloxed <template><div class"h100 w100"><div style"width:100%; heig…

基于JavaWeb的流动摊位管理系统

一、系统背景与意义 随着城市化进程的加速和市场经济的发展&#xff0c;流动摊位已经成为城市商业活动中不可或缺的一部分。然而&#xff0c;传统的流动摊位管理方式存在诸多弊端&#xff0c;如信息不透明、管理效率低下、租赁不公等。因此&#xff0c;开发一种高效、便捷、智…

自动驾驶3D目标检测综述(六)

停更了好久终于回来了&#xff08;其实是因为博主去备考期末了hh&#xff09; 这一篇接着&#xff08;五&#xff09;的第七章开始讲述第八章的内容。第八章主要介绍的是三维目标检测的高效标签。 目录 第八章 三维目标检测高效标签 一、域适应 &#xff08;一&#xff09;…

100V宽压输入反激隔离电源,适用于N道沟MOSFET或GaN或5V栅极驱动器,无需光耦合

说明: PC4411是一个隔离的反激式控制器在宽输入电压下具有高效率范围为2.7V至100V。它直接测量初级侧反激输出电压波形&#xff0c;不需要光耦合器或第三方用于调节的绕组。设置输出只需要一个电阻器电压。PC4411提供5V栅极驱动驱动外部N沟道MOSFET的电压或GaN。内部补偿和软启…

1.系统学习-线性回归

系统学习-线性回归 前言线性回归介绍误差函数梯度下降梯度下降示例 回归问题常见的评价函数1. MAE, mean absolutely error2. MSE, mean squared error3. R square &#xff08;决定系数或R方&#xff09; 机器学习建模流程模型正则化拓展阅读作业 链接: 2.系统学习-逻辑回归 …

windows使用zip包安装MySQL

windows通过zip包安装MySQL windows通过zip包安装MySQL下载MySQL的zip安装包创建安装目录和数据目录解压zip安装包创建配置目录 etc 和 配置文件 my.ini安装MySQL进入解压后的bin目录执行命令初始化执行命令安装 验证安装查看服务已安装 启动MySQL查看服务运行情况修改密码创建…

【Postgresql】数据库忘记密码时,重置密码 + 局域网下对外开放访问设置

【Postgresql】数据库忘记密码时,重置密码 + 局域网下对外开放访问设置 问题场景数据库忘记密码时,重置密码局域网下对外开放访问设置问题场景 Postgresql可支持复杂查询,支持较多的数据类型,在生产中较为使用。但有时在局域网下,想通过外部连接使用数据库,可能会出现数…

大模型-使用Ollama+Dify在本地搭建一个专属于自己的聊天助手与知识库

大模型-使用OllamaDify在本地搭建一个专属于自己的知识库 1、本地安装Dify2、本地安装Ollama并解决跨越问题3、使用Dify搭建聊天助手4、使用Dify搭建本地知识库 1、本地安装Dify 参考往期博客&#xff1a;https://guoqingru.blog.csdn.net/article/details/144683767 2、本地…

UE5 崩溃问题汇总!!!

Using bundled DotNet SDK version: 6.0.302 ERROR: UnrealBuildTool.dll not found in "..\..\Engine\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.dll" 在你遇到这种极奇崩溃的BUG &#xff0c;难以解决的时候。 尝试了N种方法&#xff0c;都不行的解决方法。…

数字IC前端学习笔记:脉动阵列的设计方法学(四)

相关阅读 数字IC前端https://blog.csdn.net/weixin_45791458/category_12173698.html?spm1001.2014.3001.5482 引言 脉动结构&#xff08;也称为脉动阵列&#xff09;表示一种有节奏地计算并通过系统传输数据的处理单元(PEs)网络。这些处理单元有规律地泵入泵出数据以保持规则…

软件工程-【软件项目管理】--期末复习题汇总

一、单项选择题 &#xff08;1&#xff09;赶工一个任务时&#xff0c;你应该关注&#xff08; C &#xff09; A. 尽可能多的任务 B. 非关键任务 C. 加速执行关键路径上的任务 D. 通过成本最低化加速执行任务 &#xff08;2&#xff09;下列哪个不是项目管理计划的一部分&…

【Git学习】windows系统下git init后没有看到生成的.git文件夹

[问题] git init 命令后看不到.git文件夹 [原因] 文件夹设置隐藏 [解决办法] Win11 win10