毕业设计实战:工作量统计系统设计与实现(SpringBoot+Vue精简版)

42 阅读8分钟

毕业设计实战:工作量统计系统设计与实现(SpringBoot+Vue精简版)

在开发“工作量统计系统”时,曾因“工作量计算公式未与教工类型关联”踩过关键坑——初期所有教工采用统一计算标准,未区分教学、科研、行政等不同类型,导致计算结果严重失真,耗费2天重构计算逻辑才解决问题📊。本文将系统拆解为精简实用的实施指南。

一、需求分析:聚焦核心统计场景

工作量统计系统最忌“大而全”。明确“系主任-教师-管理员”三方核心需求是关键。

1. 核心业务流程

工作量统计流程:
教师提交工作记录 → 系主任审核 → 系统自动计算 → 生成统计报表 → 管理员汇总

核心统计维度:
1. 按教工类型:教学型、科研型、行政型、混合型
2. 按时间周期:周统计、月统计、学期统计、年度统计
3. 按工作性质:教学工作量、科研工作量、社会工作、行政工作

2. 核心功能设计(精简版)

角色核心功能关键要点
教师提交工作记录、查看个人统计、下载报表记录需包含:类型、时长、内容、附件
系主任审核工作记录、查看部门统计、批量操作需设置审核流程,支持批量通过/驳回
管理员系统配置、数据汇总、报表生成、权限管理支持工作量计算公式自定义

3. 核心计算公式设计

// 工作量计算公式(简化版)
总工作量 = 教学工作量 + 科研工作量 + 行政工作量

// 教学工作量 = 标准课时 × 课程系数 × 学生系数
教学工作量 = 课时数 × 1.0 × (1 + 学生人数/100 × 0.1)

// 科研工作量 = 项目级别 × 参与排名 × 经费系数
科研工作量 = 国家级(10分) × 排名系数(主持人1.0) × 经费系数(每万0.1)

// 行政工作量 = 岗位系数 × 服务时长
行政工作量 = 岗位级别(系主任1.5) × 服务时长(小时)

二、技术选型:精简实用的技术栈

技术选型理由精简说明
Spring Boot 2.7快速启动,自动配置无需复杂XML配置
MySQL 8.0稳定可靠,支持事务核心数据存储
MyBatis Plus简化CRUD操作减少SQL编写
Vue 2 + ElementUI组件丰富,上手快管理后台开发
EasyExcel报表导出性能好替代传统POI

三、数据库设计:核心表结构(精简版)

-- 教工信息表
CREATE TABLE teacher (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    teacher_no VARCHAR(20) UNIQUE NOT NULL COMMENT '工号',
    name VARCHAR(50) NOT NULL COMMENT '姓名',
    department_id BIGINT NOT NULL COMMENT '部门ID',
    teacher_type TINYINT NOT NULL COMMENT '类型:1教学 2科研 3行政 4混合',
    base_coefficient DECIMAL(4,2) DEFAULT 1.0 COMMENT '基础系数',
    status TINYINT DEFAULT 1 COMMENT '状态:1正常 2离职'
) COMMENT='教工信息表';

-- 工作量记录表(核心表)
CREATE TABLE workload_record (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    record_no VARCHAR(30) UNIQUE NOT NULL COMMENT '记录编号',
    teacher_id BIGINT NOT NULL COMMENT '教工ID',
    work_type TINYINT NOT NULL COMMENT '工作类型:1教学 2科研 3行政',
    
    -- 工作详情
    work_date DATE NOT NULL COMMENT '工作日期',
    work_hours DECIMAL(5,2) NOT NULL COMMENT '工作时长',
    work_content VARCHAR(500) COMMENT '工作内容',
    attachment VARCHAR(500) COMMENT '附件路径',
    
    -- 计算参数
    coefficient DECIMAL(4,2) DEFAULT 1.0 COMMENT '工作系数',
    calculated_score DECIMAL(8,2) COMMENT '计算得分',
    
    -- 审核状态
    status TINYINT DEFAULT 1 COMMENT '状态:1待提交 2待审核 3已通过 4已驳回',
    approver_id BIGINT COMMENT '审核人',
    approval_time DATETIME COMMENT '审核时间',
    approval_remark VARCHAR(200) COMMENT '审核意见',
    
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_teacher_date (teacher_id, work_date),
    INDEX idx_status (status)
) COMMENT='工作量记录表';

-- 工作量统计表(汇总结果)
CREATE TABLE workload_statistic (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    teacher_id BIGINT NOT NULL,
    statistic_month VARCHAR(7) NOT NULL COMMENT '统计月份YYYY-MM',
    
    -- 各类工作量汇总
    teaching_score DECIMAL(8,2) DEFAULT 0 COMMENT '教学工作量',
    research_score DECIMAL(8,2) DEFAULT 0 COMMENT '科研工作量',
    admin_score DECIMAL(8,2) DEFAULT 0 COMMENT '行政工作量',
    total_score DECIMAL(8,2) DEFAULT 0 COMMENT '总工作量',
    
    -- 排名信息
    department_rank INT COMMENT '部门排名',
    total_rank INT COMMENT '全校排名',
    
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_teacher_month (teacher_id, statistic_month)
) COMMENT='工作量月度统计表';

-- 计算公式配置表
CREATE TABLE formula_config (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    formula_type VARCHAR(20) NOT NULL COMMENT '公式类型:teaching/research/admin',
    formula_expression VARCHAR(500) NOT NULL COMMENT '公式表达式',
    variables_desc TEXT COMMENT '变量说明',
    is_active BOOLEAN DEFAULT TRUE COMMENT '是否生效',
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP
) COMMENT='计算公式配置表';

四、核心业务实现(精简代码)

1. 工作量计算服务

@Service
public class WorkloadCalculateService {
    
    @Autowired
    private FormulaConfigMapper formulaMapper;
    
    /**
     * 计算单条记录工作量
     */
    public BigDecimal calculateRecordScore(WorkloadRecord record) {
        // 根据工作类型获取计算公式
        FormulaConfig formula = formulaMapper.selectByType(
            getFormulaType(record.getWorkType()));
        
        if (formula == null) {
            throw new BusinessException("未找到计算公式");
        }
        
        // 构建计算参数
        Map<String, Object> params = new HashMap<>();
        params.put("hours", record.getWorkHours());
        params.put("coefficient", record.getCoefficient());
        params.put("teacherType", record.getTeacher().getTeacherType());
        
        // 解析并计算公式(简化实现)
        return evaluateFormula(formula.getFormulaExpression(), params);
    }
    
    /**
     * 月度工作量统计
     */
    @Transactional
    public void calculateMonthlyStatistic(Long teacherId, String month) {
        // 查询该月所有已通过记录
        List<WorkloadRecord> records = recordMapper.selectMonthlyRecords(
            teacherId, month, RecordStatus.APPROVED);
        
        WorkloadStatistic statistic = new WorkloadStatistic();
        statistic.setTeacherId(teacherId);
        statistic.setStatisticMonth(month);
        
        // 分类统计
        for (WorkloadRecord record : records) {
            BigDecimal score = calculateRecordScore(record);
            
            switch (record.getWorkType()) {
                case 1: // 教学
                    statistic.setTeachingScore(
                        statistic.getTeachingScore().add(score));
                    break;
                case 2: // 科研
                    statistic.setResearchScore(
                        statistic.getResearchScore().add(score));
                    break;
                case 3: // 行政
                    statistic.setAdminScore(
                        statistic.getAdminScore().add(score));
                    break;
            }
        }
        
        // 计算总分
        BigDecimal total = statistic.getTeachingScore()
            .add(statistic.getResearchScore())
            .add(statistic.getAdminScore());
        statistic.setTotalScore(total);
        
        // 保存统计结果
        statisticMapper.insertOrUpdate(statistic);
    }
}

2. 批量审核功能

@Service
public class WorkloadApproveService {
    
    /**
     * 批量审核工作记录
     */
    @Transactional
    public ApiResult batchApprove(List<Long> recordIds, Long approverId, 
                                 boolean approved, String remark) {
        if (recordIds.isEmpty()) {
            return ApiResult.error("请选择要审核的记录");
        }
        
        List<WorkloadRecord> records = recordMapper.selectBatchIds(recordIds);
        Date now = new Date();
        
        for (WorkloadRecord record : records) {
            // 状态校验
            if (record.getStatus() != RecordStatus.PENDING) {
                continue;
            }
            
            record.setApproverId(approverId);
            record.setApprovalTime(now);
            record.setApprovalRemark(remark);
            record.setStatus(approved ? 
                RecordStatus.APPROVED : RecordStatus.REJECTED);
            
            recordMapper.updateById(record);
            
            // 记录操作日志
            logService.addApproveLog(record, approverId, approved);
        }
        
        return ApiResult.success(String.format("已处理 %d 条记录", records.size()));
    }
}

3. 统计报表生成

@Service
public class ReportService {
    
    @Autowired
    private EasyExcel easyExcel;
    
    /**
     * 生成部门工作量报表
     */
    public void exportDepartmentReport(Long departmentId, String month, 
                                      HttpServletResponse response) {
        // 查询部门统计数据
        List<WorkloadStatistic> statistics = statisticMapper
            .selectDepartmentStatistics(departmentId, month);
        
        // 设置响应头
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode(
            String.format("工作量统计_%s.xlsx", month), "UTF-8");
        response.setHeader("Content-disposition", 
            "attachment;filename=" + fileName);
        
        // 导出Excel
        try (ExcelWriter excelWriter = easyExcel.write(response.getOutputStream())) {
            WriteSheet writeSheet = easyExcel.writerSheet("工作量统计").build();
            excelWriter.write(statistics, writeSheet);
        } catch (IOException e) {
            throw new BusinessException("导出失败");
        }
    }
}

五、前端核心页面(Vue精简版)

1. 工作记录提交页面

<template>
  <div class="record-submit">
    <el-form :model="recordForm" :rules="rules" ref="formRef">
      <el-form-item label="工作类型" prop="workType">
        <el-select v-model="recordForm.workType" placeholder="请选择">
          <el-option label="教学工作" :value="1" />
          <el-option label="科研工作" :value="2" />
          <el-option label="行政工作" :value="3" />
        </el-select>
      </el-form-item>
      
      <el-form-item label="工作日期" prop="workDate">
        <el-date-picker v-model="recordForm.workDate" type="date" />
      </el-form-item>
      
      <el-form-item label="工作时长" prop="workHours">
        <el-input-number v-model="recordForm.workHours" :min="0.5" :step="0.5" />
        <span class="tip">单位:小时</span>
      </el-form-item>
      
      <el-form-item label="工作内容" prop="workContent">
        <el-input v-model="recordForm.workContent" type="textarea" :rows="4" />
      </el-form-item>
      
      <el-form-item>
        <el-button type="primary" @click="submitRecord">提交</el-button>
        <el-button @click="resetForm">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      recordForm: {
        workType: null,
        workDate: null,
        workHours: 1,
        workContent: ''
      },
      rules: {
        workType: [{ required: true, message: '请选择工作类型' }],
        workDate: [{ required: true, message: '请选择工作日期' }],
        workContent: [{ required: true, message: '请填写工作内容' }]
      }
    }
  },
  methods: {
    async submitRecord() {
      try {
        await this.$refs.formRef.validate()
        await this.$api.workload.submitRecord(this.recordForm)
        this.$message.success('提交成功')
        this.resetForm()
      } catch (error) {
        this.$message.error('提交失败')
      }
    },
    resetForm() {
      this.$refs.formRef.resetFields()
    }
  }
}
</script>

2. 工作量统计页面

<template>
  <div class="statistic-page">
    <!-- 筛选条件 -->
    <el-card class="filter-card">
      <el-form :inline="true">
        <el-form-item label="统计月份">
          <el-date-picker v-model="query.month" type="month" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="search">查询</el-button>
          <el-button @click="exportExcel">导出Excel</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    
    <!-- 统计表格 -->
    <el-table :data="statisticList" border>
      <el-table-column prop="teacherName" label="姓名" width="100" />
      <el-table-column prop="departmentName" label="部门" width="120" />
      <el-table-column prop="teachingScore" label="教学工作量" width="120" />
      <el-table-column prop="researchScore" label="科研工作量" width="120" />
      <el-table-column prop="adminScore" label="行政工作量" width="120" />
      <el-table-column prop="totalScore" label="总工作量" width="120">
        <template slot-scope="scope">
          <strong class="total-score">{{ scope.row.totalScore }}</strong>
        </template>
      </el-table-column>
      <el-table-column prop="departmentRank" label="部门排名" width="100" />
    </el-table>
  </div>
</template>

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

六、系统测试要点

1. 核心功能测试

测试场景测试要点预期结果
工作量计算不同类型、不同参数的工作记录计算结果符合公式定义
批量审核选择多条记录进行批量操作所有选中记录状态更新
月度统计统计指定月份数据生成准确的汇总报表
公式配置修改计算公式表达式新记录使用新公式计算

2. 数据准确性验证

  • 边界值测试:0小时、超大时长、负数时长
  • 类型转换测试:教学、科研、行政类型切换
  • 并发测试:多人同时提交、审核、统计

七、答辩准备要点

1. 演示流程(5分钟)

  1. 教师端演示(1分钟)

    • 提交教学工作记录
    • 查看个人统计报表
  2. 系主任端演示(2分钟)

    • 批量审核待审记录
    • 查看部门统计排名
  3. 管理员端演示(2分钟)

    • 修改计算公式
    • 导出全校统计报表

2. 重点问题准备

Q: 工作量计算公式如何适应不同学院的需求? A: 采用公式配置化设计:

  • 每类工作可独立配置计算公式
  • 支持按部门设置不同的计算参数
  • 公式修改后,历史数据保持不变

Q: 如何确保统计数据的准确性? A: 三层校验机制:

  1. 提交时:基础数据校验
  2. 审核时:内容真实性审核
  3. 统计时:数据完整性检查

Q: 系统如何处理大量数据统计? A: 优化策略:

  • 月度统计结果缓存
  • 分页加载大数据量
  • 异步生成报表文件

3. 创新点展示

  1. 公式配置化:支持动态调整计算规则
  2. 批量处理:大幅提升审核效率
  3. 实时统计:数据变更立即更新排名

结语

工作量统计系统的核心在于计算准确流程高效。开发时需重点关注: