SpringBoot+Vue3 企业日报周报系统设计实战:2张表、3种汇报类型、填写统计+逾期追踪——让工作汇报不再是负担
📦 源码1:ruoyi-office-vben |📦 源码2:ruoyi-office |📦 源码3:ruoyi-office
工作汇报是企业管理的"日常必修课"——日报记录当天产出,周报总结阶段成果,月报复盘整体目标。但大多数团队的汇报方式还停留在"微信群贴文字"或"邮件发 Word"的阶段:格式各异、无法统计、补交漏交无人追踪。RuoYi Office 用 2 张表、3 种汇报类型、1 套统计看板,构建了从填写→审批→统计→逾期追踪的工作汇报闭环。
引言:工作汇报到底难在哪?
"日报不就是每天写几句话吗?"——初次做这个需求的开发者往往这么想。但真正动手会发现,看似简单的工作汇报隐藏着不少设计难题:
日报、周报、月报三种形态,要不要建三张表? 日报按天、周报按周次、月报按月份,周期不同、字段不同。如果分三张表,代码量翻三倍;如果合一张表,又要解决字段复用和条件显示的问题。
已完成的工作和下一步计划,结构不同但要一起管理。 已完成有"完成进度",计划没有;两者都是多条记录,要支持动态增删。
谁没交?逾期几天了? 领导最关心的不是写了什么,而是"谁还没交"。需要按部门汇总统计,实时计算每人的填写率和逾期天数。
审批后的表单锁定。 日报提交后走审批流程,审批期间和审批通过后表单都要自动切换为只读。
| 痛点 | 传统方式 | 后果 |
|---|---|---|
| 格式不统一 | 微信群自由发言 | 信息零散,无法结构化检索 |
| 无法追踪漏交 | 人工逐个核对 | 管理者每天花 30 分钟"催日报" |
| 日周月分散管理 | 三套模板/三个文件夹 | 维护成本高,数据孤岛 |
| 无统计维度 | Excel 手工统计 | 月底才发现某员工一个月没交过周报 |
本文将拆解 RuoYi Office 如何用"三报合一"的设计,一套表结构、一套前端组件、一套审批流程,同时解决日报、周报、月报的填写与管理问题。
一、业务设计:三报合一的核心抽象
1.1 为什么不建三张表?
拿到"日报周报月报"需求的第一反应是建三张表——oa_daily_report、oa_weekly_report、oa_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 汇报流程概览

▲ 工作汇报生命周期:填写 → 保存/提交 → BPM 审批 → 统计追踪
二、系统设计:模块组成与设计决策
2.1 模块组成
| 子模块 | 功能 | 面向角色 |
|---|---|---|
| 工作日报列表 | 日报专属列表页,固定 reportType=1 | 员工 |
| 工作周报列表 | 周报专属列表页,固定 reportType=2,多显示"周次"列 | 员工 |
| 工作月报列表 | 月报专属列表页,固定 reportType=3,多显示"月份"列 | 员工 |
| 工作汇报表单 | 统一的新建/编辑/详情表单,动态适配三种类型 | 员工 |
| 汇报统计 | 按部门统计填写率,支持查看已填/未填明细及逾期天数 | 管理者 |
2.2 核心设计决策
| 决策点 | 方案 | 理由 |
|---|---|---|
| 日/周/月共用表结构 | reportType 字段 + 周次/月份互斥 | 90%+ 字段重合,避免三套代码 |
| 工作项子表设计 | itemType 区分已完成/计划 | 结构相似,一张表简化关联查询 |
| 列表页拆分策略 | 三个菜单指向同一组件,路由参数决定类型 | 用户视角清晰,代码层面共用 |
| 日期联动 | 选择周次/月份自动填充开始/结束日期 | 减少手工输入,保证日期范围正确 |
| 统计逾期计算 | 按工作日计算应填数,实时比对已填记录 | 排除周末,贴合实际工作安排 |
| BPM 集成 | FlowBillService 回调 + processStatus 驱动只读 | 统一审批模式,表单自动锁定 |
三、PC 端功能实现
3.1 工作日报列表

▲ 工作日报列表:展示单据编号、状态、汇报类型、标题、日期范围、申请人和部门信息
日报列表的设计要点:
- 路由驱动类型:列表组件通过路由名称/路径/参数自动识别当前类型,
resolveFixedReportType()方法优先检查菜单名是否包含"日报"/"周报"/"月报"关键词 - 查询自动加 creator:分页查询强制带入当前用户 ID,即"只看自己的",避免数据越权
- 批量删除保护:只有
processStatus在可删除状态集合内的单据才允许删除,已审批通过的单据不可删
3.2 工作周报列表

▲ 工作周报列表:与日报共用组件,自动多显示"汇报周次"列,搜索条件也动态调整
同一组件、不同表现:周报列表会自动:
- 表格多插入一列"汇报周次"
- 搜索条件增加"汇报周次"输入框,隐藏"汇报月份"
- 新建时固定
reportType=2
3.3 工作汇报表单(详情页)

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

▲ 汇报统计:左侧部门树选择部门,右上角 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);
}
}
这段逻辑在 saveWorkReport 和 submitWorkReport 时都会执行,确保周次/月份字段始终与 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
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| bill_code | varchar(32) | 单据编号,格式 OA111-{日期}{序号} |
| process_instance_id | varchar(64) | BPM 流程实例 ID |
| process_status | tinyint | 流程状态(未发起/审批中/通过/拒绝) |
| report_type | tinyint | 汇报类型:1=日报 2=周报 3=月报 |
| report_week | varchar(10) | 周次,格式 2026-12(仅周报) |
| report_month | varchar(7) | 月份,格式 2026-03(仅月报) |
| title | varchar(200) | 标题(可选) |
| start_date | date | 汇报开始日期 |
| end_date | date | 汇报结束日期 |
| today_content | text | 工作总结(补充说明) |
| plan_content | text | 工作计划(补充说明) |
| issue_content | text | 问题/协调事项 |
| remark | varchar(500) | 备注 |
| creator_name | varchar(30) | 申请人姓名(冗余) |
| dept_id | bigint | 所属部门 ID |
| dept_name | varchar(50) | 所属部门名称(冗余) |
| company_id | bigint | 所属公司 ID |
| company_name | varchar(50) | 所属公司名称(冗余) |
设计要点:
report_week和report_month互斥使用,后端fillReportPeriodFields保证只有对应类型的字段有值creator_name、dept_name、company_name冗余存储,避免列表查询时多次关联用户表和部门表today_content、plan_content为文本区补充说明,具体工作项存储在子表中
7.2 明细表 oa_work_report_item
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| report_id | bigint | 关联主表 ID |
| item_type | tinyint | 工作项类型:1=已完成 2=计划 |
| content | text | 工作内容/计划内容 |
| progress | int | 完成进度 0-100(仅已完成类型有效) |
| sort | int | 排序序号 |
设计要点:
item_type区分两类工作项,前端展示时按filter(i => i.itemType === 1)和filter(i => i.itemType === 2)分组progress对计划类型无意义,前端不显示进度列- 保存时采用"先删后插"的全量替换策略,避免复杂的差异更新逻辑
八、技术亮点总结
| 设计要点 | 实现方式 | 价值 |
|---|---|---|
| 三报合一 | reportType + 动态字段显隐 | 代码量减少 60%,新增报类型零成本 |
| 智能日期联动 | 前端 handleFormValuesChange 监听 | 选周次/月份自动填日期,零手工输入 |
| 工作日计算 | 遍历排除周六日 | 应填数精准,不误判周末逾期 |
| 子表分区展示 | 同表不同 itemType + 两个 VxeGrid | 数据集中存储,展示逻辑清晰 |
| 逾期追踪 | ChronoUnit.DAYS.between 实时计算 | 管理者即时发现漏交 |
| 表单状态驱动 | computeBusinessFormReadonly 一行控制 | 全表单联动只读,杜绝误改 |
| 路由驱动列表 | 同一组件根据路由名/参数切换展示 | 三个菜单共用一套代码 |
| 流程变量透传 | reportType 透传到 BPM | 支持按汇报类型走不同审批路径 |
| 全量替换子表 | 先删后插 | 避免复杂的差异对比更新 |
| 冗余姓名/部门 | 主表存储 creatorName、deptName | 列表查询无需多表关联 |
九、快速体验
操作路径
| 功能 | 菜单路径 |
|---|---|
| 工作日报 | OA → 工作汇报 → 工作日报列表 |
| 工作周报 | OA → 工作汇报 → 工作周报列表 |
| 工作月报 | OA → 工作汇报 → 工作月报列表 |
| 汇报统计 | OA → 工作汇报 → 汇报统计 |
推荐体验流程
- 进入"工作日报列表",点击"新增"创建一份日报
- 在表单中添加 2-3 条已完成工作(拖动进度滑块到 80%)
- 添加 1-2 条工作计划
- 点击"提交",触发 BPM 审批
- 切换到"工作周报列表",新增一份周报,观察周次下拉框和日期联动
- 进入"汇报统计",选择部门和日期范围,查看填写率统计
- 审批通过后再次打开日报详情,确认表单已变为只读
源码仓库
| 仓库 | 地址 |
|---|---|
| 后端(Java) | ruoyi-office |
| 前端(Vue3) | ruoyi-office-vben |
| GitHub 镜像 | ruoyi-office |
结语
工作汇报看似是最简单的 OA 模块,但"三报合一"的抽象设计、智能日期联动、按工作日计算应填数、逾期天数追踪这些细节,决定了系统是"能用"还是"好用"。好的日报系统不是让员工更痛苦地写,而是让管理者更高效地看。 这种"主表+子表+统计看板"的设计模式,同样适用于培训记录、巡检日志、客户拜访报告等场景。
💡 想要体验 RuoYi Office 的强大功能?
🌐 在线演示:ruoyioffice.com/web/(账号 admin / admin123)
💬 技术咨询:添加💬 17156169080,备注「RuoYi Office」
⭐ 如果觉得不错,请给个 Star 支持一下!