SpringBoot+Vue3 企业日报周报系统设计实战:2张表、3种汇报类型、填写统计+逾期追踪——让工作汇报不再是负担

0 阅读16分钟

SpringBoot+Vue3 企业日报周报系统设计实战:2张表、3种汇报类型、填写统计+逾期追踪——让工作汇报不再是负担

📦 源码1ruoyi-office-vben |📦 源码2ruoyi-office |📦 源码3ruoyi-office

工作汇报是企业管理的"日常必修课"——日报记录当天产出,周报总结阶段成果,月报复盘整体目标。但大多数团队的汇报方式还停留在"微信群贴文字"或"邮件发 Word"的阶段:格式各异、无法统计、补交漏交无人追踪。RuoYi Office 用 2 张表、3 种汇报类型、1 套统计看板,构建了从填写→审批→统计→逾期追踪的工作汇报闭环。

引言:工作汇报到底难在哪?

"日报不就是每天写几句话吗?"——初次做这个需求的开发者往往这么想。但真正动手会发现,看似简单的工作汇报隐藏着不少设计难题:

日报、周报、月报三种形态,要不要建三张表? 日报按天、周报按周次、月报按月份,周期不同、字段不同。如果分三张表,代码量翻三倍;如果合一张表,又要解决字段复用和条件显示的问题。

已完成的工作和下一步计划,结构不同但要一起管理。 已完成有"完成进度",计划没有;两者都是多条记录,要支持动态增删。

谁没交?逾期几天了? 领导最关心的不是写了什么,而是"谁还没交"。需要按部门汇总统计,实时计算每人的填写率和逾期天数。

审批后的表单锁定。 日报提交后走审批流程,审批期间和审批通过后表单都要自动切换为只读。

痛点传统方式后果
格式不统一微信群自由发言信息零散,无法结构化检索
无法追踪漏交人工逐个核对管理者每天花 30 分钟"催日报"
日周月分散管理三套模板/三个文件夹维护成本高,数据孤岛
无统计维度Excel 手工统计月底才发现某员工一个月没交过周报

本文将拆解 RuoYi Office 如何用"三报合一"的设计,一套表结构、一套前端组件、一套审批流程,同时解决日报、周报、月报的填写与管理问题。


一、业务设计:三报合一的核心抽象

1.1 为什么不建三张表?

拿到"日报周报月报"需求的第一反应是建三张表——oa_daily_reportoa_weekly_reportoa_monthly_report。但仔细分析会发现:

对比维度日报周报月报
时间范围1 天1 周1 个月
特有字段周次(如 2026-12)月份(如 2026-03)
共有字段标题、开始/结束日期、工作总结、计划、问题、备注同左同左
子表结构已完成工作 + 工作计划同左同左
审批流程BPM 审批同左同左

核心发现:三者的字段重合度超过 90%。 周报比日报多一个 reportWeek,月报多一个 reportMonth,其余完全一致。分三张表意味着三套 Controller、三套 Service、三套前端页面——维护成本翻三倍。

RuoYi Office 的方案:一张主表 + reportType 字段区分类型。 周次和月份字段互斥使用,前端通过 reportType 动态显示/隐藏。

1.2 已完成 vs 计划:子表的 itemType 区分

工作汇报的内容分两部分:

  • 已完成工作:包含工作内容和完成进度(0-100%)
  • 工作计划:只有计划内容,没有进度

两者结构相似但不完全相同,如果拆成两张子表又会引入不必要的复杂度。RuoYi Office 用 一张子表 + itemType 字段 解决:itemType=1 为已完成,itemType=2 为计划。前端展示时分成两个独立的表格区域。

1.3 汇报流程概览

image.png

▲ 工作汇报生命周期:填写 → 保存/提交 → BPM 审批 → 统计追踪


二、系统设计:模块组成与设计决策

2.1 模块组成

子模块功能面向角色
工作日报列表日报专属列表页,固定 reportType=1员工
工作周报列表周报专属列表页,固定 reportType=2,多显示"周次"列员工
工作月报列表月报专属列表页,固定 reportType=3,多显示"月份"列员工
工作汇报表单统一的新建/编辑/详情表单,动态适配三种类型员工
汇报统计按部门统计填写率,支持查看已填/未填明细及逾期天数管理者

2.2 核心设计决策

决策点方案理由
日/周/月共用表结构reportType 字段 + 周次/月份互斥90%+ 字段重合,避免三套代码
工作项子表设计itemType 区分已完成/计划结构相似,一张表简化关联查询
列表页拆分策略三个菜单指向同一组件,路由参数决定类型用户视角清晰,代码层面共用
日期联动选择周次/月份自动填充开始/结束日期减少手工输入,保证日期范围正确
统计逾期计算按工作日计算应填数,实时比对已填记录排除周末,贴合实际工作安排
BPM 集成FlowBillService 回调 + processStatus 驱动只读统一审批模式,表单自动锁定

三、PC 端功能实现

3.1 工作日报列表

workreport-daily-list.png

▲ 工作日报列表:展示单据编号、状态、汇报类型、标题、日期范围、申请人和部门信息

日报列表的设计要点:

  • 路由驱动类型:列表组件通过路由名称/路径/参数自动识别当前类型,resolveFixedReportType() 方法优先检查菜单名是否包含"日报"/"周报"/"月报"关键词
  • 查询自动加 creator:分页查询强制带入当前用户 ID,即"只看自己的",避免数据越权
  • 批量删除保护:只有 processStatus 在可删除状态集合内的单据才允许删除,已审批通过的单据不可删

3.2 工作周报列表

workreport-weekly-list.png

▲ 工作周报列表:与日报共用组件,自动多显示"汇报周次"列,搜索条件也动态调整

同一组件、不同表现:周报列表会自动:

  1. 表格多插入一列"汇报周次"
  2. 搜索条件增加"汇报周次"输入框,隐藏"汇报月份"
  3. 新建时固定 reportType=2

3.3 工作汇报表单(详情页)

image.png

▲ 工作汇报详情页:上方基本信息(类型/日期/标题/摘要),下方分"已完成工作"和"工作计划"两个子表

表单页的核心交互设计:

  • 类型驱动日期:选择"日报"时开始/结束日期默认为今天;选择"周报"时联动本周一到周日;选择"月报"时联动本月 1 日到月末
  • 周次/月份联动:切换周次下拉框,开始/结束日期自动跟随变化;月份选择器同理
  • 子表动态编辑:已完成工作项每行包含"工作内容"输入框和"完成进度"滑块(0-100%);计划项只有"计划内容"
  • 审批后只读:通过 computeBusinessFormReadonly() 计算只读状态,提交后所有输入框、滑块、按钮自动禁用
  • 最低提交校验:提交时校验"已完成"和"计划"至少有一项内容,防止提交空白汇报

3.4 汇报统计看板

image.png

▲ 汇报统计:左侧部门树选择部门,右上角 Segmented 切换日报/周报/月报,日期范围筛选

统计页面的设计亮点:

  • 三级部门树:支持多级组织架构(总公司→分公司→部门),点击部门节点加载该部门的统计数据
  • 三种类型切换:Segmented 组件实现日报/周报/月报一键切换,切换后自动重新计算统计数据
  • 五列统计指标:姓名、部门、应填数量、已填数量、未填数量、填写率(百分比+进度条)
  • 明细弹窗:点击人员行可查看已填列表(含单据链接)和未填列表(含逾期天数标红)

四、流程设计:BPM 集成

4.1 流程编排

工作汇报的 BPM 集成采用与其他 OA 模块相同的 FlowBillService 回调模式:

环节动作系统行为
保存(暂存)填写表单,点击"保存"写入数据库,processStatus=未发起,不启动流程
提交填写表单,点击"提交"写入数据库 + 启动 BPM 流程实例,processStatus=审批中
审批通过审批人在任务中心通过BPM 回调 updateProcessStatus,更新为"已通过"
审批拒绝审批人驳回回调更新为"已拒绝",制单人可修改后重新提交
撤回制单人主动撤回调用 withdrawProcessToStart,状态回退

4.2 流程变量透传

提交流程时,除了标准的单据变量外,还额外透传了 reportType 作为流程变量:

Map<String, Object> variables = BpmProcessVariableUtils.buildBillVariables(saveReqVO);
variables.put(PV_REPORT_TYPE, saveReqVO.getReportType());

这样流程中可以根据 reportType 走不同的审批路径——比如日报直接主管审批,周报需要经理审批,月报需要总监审批。

4.3 FlowBillService 回调

@Override
public OaBillTypeEnum getSupportedBillType() {
    return OaBillTypeEnum.OA_WORK_REPORT;
}

@Override
public void updateProcessStatus(String businessKey, Integer status) {
    workReportMapper.updateById(
        new WorkReportDO().setId(Long.parseLong(businessKey))
                          .setProcessStatus(status)
    );
}

BPM 引擎在审批节点完成时,通过 FlowBillService.updateProcessStatus 回调业务系统,将审批结果同步到主表的 processStatus 字段。前端读取这个状态驱动表单的只读/可编辑切换。


五、后端核心实现

5.1 数据模型

主表 oa_work_report明细表 oa_work_report_item 的关系:

关系说明
主表:明细 = 1:N一份汇报包含多条工作项
reportId 关联明细表通过 reportId 指向主表 id
itemType 区分1=已完成工作,2=工作计划
全量替换每次保存时删除旧明细,重新插入

5.2 单据编号生成

工作汇报的编号格式为 OA111-{yyyyMMdd}{5位序号},通过框架统一的 BillCodeUtils 生成,底层基于 Redis INCR 保证并发安全:

if (StrUtil.isBlank(saveReqVO.getBillCode())) {
    saveReqVO.setBillCode(BillCodeUtils.generateBillCode(
        OaBillTypeEnum.OA_WORK_REPORT.getBillCodePrefix()
    ));
}

5.3 周次/月份自动填充

private void fillReportPeriodFields(WorkReportDO report) {
    Integer reportType = report.getReportType();
    LocalDate startDate = report.getStartDate();
    if (reportType == null || startDate == null) return;

    if (reportType == 2) {
        // 周报:按 startDate 计算 ISO 周次,格式 "2026-12"
        int weekNumber = startDate.get(WeekFields.ISO.weekOfYear());
        report.setReportWeek(startDate.getYear() + "-" 
            + String.format("%02d", weekNumber));
        report.setReportMonth(null);
    } else if (reportType == 3) {
        // 月报:按 startDate 取年月,格式 "2026-03"
        report.setReportMonth(startDate.format(
            DateTimeFormatter.ofPattern("yyyy-MM")));
        report.setReportWeek(null);
    } else {
        report.setReportWeek(null);
        report.setReportMonth(null);
    }
}

这段逻辑在 saveWorkReportsubmitWorkReport 时都会执行,确保周次/月份字段始终与 startDate 保持一致,前端传什么都会被后端校正。

5.4 统计计算:应填数 vs 已填数

统计是日报周报系统的核心价值——让管理者一眼看到谁没交。核心逻辑分三步:

第一步:计算应填数量

private int calcExpectedCount(int reportType, LocalDate start, LocalDate end) {
    if (reportType == 1) {
        // 日报:统计区间内的工作日天数(排除周六日)
        int count = 0;
        LocalDate date = start;
        while (!date.isAfter(end)) {
            DayOfWeek dow = date.getDayOfWeek();
            if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) {
                count++;
            }
            date = date.plusDays(1);
        }
        return count;
    } else if (reportType == 2) {
        // 周报:统计区间内涉及的不重复 ISO 周数
        Set<String> weeks = new HashSet<>();
        // ... 遍历日期范围,收集所有 ISO 周标识
        return weeks.size();
    } else {
        // 月报:统计区间内涉及的月份数
        // ...
        return months.size();
    }
}

第二步:查询已填数量

creator(用户ID)分组统计,查询指定日期范围和汇报类型的已提交记录数。

第三步:生成未填列表与逾期天数

// 未填项中计算逾期天数
UnfilledItem item = new UnfilledItem();
item.setDate(date);
if (date.isBefore(today)) {
    item.setOverdueDays((int) ChronoUnit.DAYS.between(date, today));
} else {
    item.setOverdueDays(0);
}

逾期天数 = 当前日期 - 应填日期。如果应填日期还没到(未来日期),逾期天数为 0。

5.5 提交流程

@Override
@Transactional(rollbackFor = Exception.class)
public Long submitWorkReport(WorkReportSaveReqVO saveReqVO) {
    // 1. 填充周次/月份
    fillReportPeriodFields(workReport);
    // 2. 设置流程状态为审批中
    saveReqVO.setProcessStatus(BpmProcessInstanceStatus.RUNNING.getStatus());
    // 3. 保存主表+明细+附件
    Long id = saveOrUpdate(saveReqVO);
    // 4. 启动 BPM 流程实例
    Map<String, Object> variables = BpmProcessVariableUtils
        .buildBillVariables(saveReqVO);
    variables.put(PV_REPORT_TYPE, saveReqVO.getReportType());
    String processInstanceId = bpmProcessInstanceApi.submitProcessInstance(
        Long.parseLong(workReport.getCreator()),
        new BpmProcessInstanceCreateReqDTO()
            .setProcessDefinitionKey("oa_work_report")
            .setBusinessKey(String.valueOf(id))
            .setVariables(variables)
    );
    // 5. 回写流程实例ID
    workReportMapper.updateById(
        new WorkReportDO().setId(id)
            .setProcessInstanceId(processInstanceId)
    );
    return id;
}

六、RuoYi Office 创新设计

6.1 三报合一:一套代码管日/周/月

传统做法是日报、周报、月报各建一套模块。RuoYi Office 用 reportType + 动态字段显隐,实现了:

  • 一张主表 存储所有汇报记录
  • 一个表单组件 根据类型动态显示周次选择器或月份选择器
  • 一个列表组件 根据路由参数切换标题和列配置
  • 一套 Service 处理所有类型的增删改查

这种设计的价值:后续如果要新增"季报",只需加一个 reportType=4,不需要新建任何表或组件。

6.2 智能日期联动

填写周报时,选择"2026年第12周",开始/结束日期自动填充为该周的周一到周日。前端的实现思路:

操作联动结果
切换汇报类型为"日报"开始/结束日期 = 今天
切换汇报类型为"周报"自动选中本周,日期 = 本周一~本周日
切换汇报类型为"月报"自动选中本月,日期 = 本月1日~月末
修改周次下拉框日期跟随变化为选中周的起止日期
修改月份选择器日期跟随变化为选中月的起止日期

这种联动避免了用户手动输入日期的麻烦,也杜绝了"开始日期比结束日期大"之类的低级错误。

6.3 按工作日计算应填数

日报的应填数量不是简单地用"天数差",而是排除了周六日的工作日天数。这样周五查看统计时不会把周末也算进去,避免误判为"逾期"。

6.4 子表分区展示

已完成工作和工作计划虽然存在同一张子表里,但前端用两个独立的 VxeGrid 展示:

  • 已完成工作表格:序号 + 工作内容(Input)+ 完成进度(Slider 0-100%)+ 操作
  • 工作计划表格:序号 + 计划内容(Input)+ 操作

每个表格都有独立的"添加"和"删除"按钮,互不干扰。保存时合并并标记 itemType

6.5 表单状态驱动

readonly.value = computeBusinessFormReadonly(
    props.viewType,
    props.isApproval,
    formData.value.processStatus as number
);

这一行代码决定了整个表单的可编辑性:

  • 新建/草稿状态:可编辑
  • 审批中/已通过/已拒绝:只读
  • 审批页面查看:只读

所有输入框、滑块、添加/删除按钮都受 readonly 控制,无需逐个字段设置。


七、数据结构

7.1 主表 oa_work_report

字段类型说明
idbigint主键
bill_codevarchar(32)单据编号,格式 OA111-{日期}{序号}
process_instance_idvarchar(64)BPM 流程实例 ID
process_statustinyint流程状态(未发起/审批中/通过/拒绝)
report_typetinyint汇报类型:1=日报 2=周报 3=月报
report_weekvarchar(10)周次,格式 2026-12(仅周报)
report_monthvarchar(7)月份,格式 2026-03(仅月报)
titlevarchar(200)标题(可选)
start_datedate汇报开始日期
end_datedate汇报结束日期
today_contenttext工作总结(补充说明)
plan_contenttext工作计划(补充说明)
issue_contenttext问题/协调事项
remarkvarchar(500)备注
creator_namevarchar(30)申请人姓名(冗余)
dept_idbigint所属部门 ID
dept_namevarchar(50)所属部门名称(冗余)
company_idbigint所属公司 ID
company_namevarchar(50)所属公司名称(冗余)

设计要点

  • report_weekreport_month 互斥使用,后端 fillReportPeriodFields 保证只有对应类型的字段有值
  • creator_namedept_namecompany_name 冗余存储,避免列表查询时多次关联用户表和部门表
  • today_contentplan_content 为文本区补充说明,具体工作项存储在子表中

7.2 明细表 oa_work_report_item

字段类型说明
idbigint主键
report_idbigint关联主表 ID
item_typetinyint工作项类型:1=已完成 2=计划
contenttext工作内容/计划内容
progressint完成进度 0-100(仅已完成类型有效)
sortint排序序号

设计要点

  • item_type 区分两类工作项,前端展示时按 filter(i => i.itemType === 1)filter(i => i.itemType === 2) 分组
  • progress 对计划类型无意义,前端不显示进度列
  • 保存时采用"先删后插"的全量替换策略,避免复杂的差异更新逻辑

八、技术亮点总结

设计要点实现方式价值
三报合一reportType + 动态字段显隐代码量减少 60%,新增报类型零成本
智能日期联动前端 handleFormValuesChange 监听选周次/月份自动填日期,零手工输入
工作日计算遍历排除周六日应填数精准,不误判周末逾期
子表分区展示同表不同 itemType + 两个 VxeGrid数据集中存储,展示逻辑清晰
逾期追踪ChronoUnit.DAYS.between 实时计算管理者即时发现漏交
表单状态驱动computeBusinessFormReadonly 一行控制全表单联动只读,杜绝误改
路由驱动列表同一组件根据路由名/参数切换展示三个菜单共用一套代码
流程变量透传reportType 透传到 BPM支持按汇报类型走不同审批路径
全量替换子表先删后插避免复杂的差异对比更新
冗余姓名/部门主表存储 creatorNamedeptName列表查询无需多表关联

九、快速体验

操作路径

功能菜单路径
工作日报OA → 工作汇报 → 工作日报列表
工作周报OA → 工作汇报 → 工作周报列表
工作月报OA → 工作汇报 → 工作月报列表
汇报统计OA → 工作汇报 → 汇报统计

推荐体验流程

  1. 进入"工作日报列表",点击"新增"创建一份日报
  2. 在表单中添加 2-3 条已完成工作(拖动进度滑块到 80%)
  3. 添加 1-2 条工作计划
  4. 点击"提交",触发 BPM 审批
  5. 切换到"工作周报列表",新增一份周报,观察周次下拉框和日期联动
  6. 进入"汇报统计",选择部门和日期范围,查看填写率统计
  7. 审批通过后再次打开日报详情,确认表单已变为只读

源码仓库

仓库地址
后端(Java)ruoyi-office
前端(Vue3)ruoyi-office-vben
GitHub 镜像ruoyi-office

结语

工作汇报看似是最简单的 OA 模块,但"三报合一"的抽象设计、智能日期联动、按工作日计算应填数、逾期天数追踪这些细节,决定了系统是"能用"还是"好用"。好的日报系统不是让员工更痛苦地写,而是让管理者更高效地看。 这种"主表+子表+统计看板"的设计模式,同样适用于培训记录、巡检日志、客户拜访报告等场景。


💡 想要体验 RuoYi Office 的强大功能?

🌐 在线演示ruoyioffice.com/web/(账号 admin / admin123)

💬 技术咨询:添加💬 17156169080,备注「RuoYi Office」

如果觉得不错,请给个 Star 支持一下!