SpringBoot3+Flowable+Vue3 流程评论:审批意见、单据备注和 IM 不要做成同一个盒子
🌐文档地址:https://ruoyioffice.com
📦源码1·GitHub:https://github.com/yuqing2026/ruoyi-office
📦源码2·GitCode:https://gitcode.com/zhouzhongyan/ruoyi-office
📦源码3·Gitee:https://gitee.com/yqzy1688/ruoyi-office
💬微信:17156169080(备注「RuoYi Office」)
审批人在用车单上想问一句「油费谁出」,最容易做错的是把这句话写进拒绝理由,或者丢到企业微信群里。前者会把流程打回,后者刷新页面就找不到。RuoYi Office 在同一张办理详情里拆成三套字:单据备注、流程评论、审批意见。发评论不结束任务;点拒绝才走意见;IM 开聊会离开单据。本文按能点的 Tab 和底栏按钮来配。
▲ 中间是用车申请单的 Tab 线框;沟通留在「流程评论」,结论仍走同意或拒绝;批量通过不发评论
引言:同一张单上的字,职责不一样
把「备注」做成一个大文本框,什么都往里堆,上线后会出现这些错位:
| 痛点 | 常见做法 | 后果 |
|---|---|---|
| 问一句补充材料 | 点拒绝并写理由 | 流程被打回,发起人以为被否了 |
| 催一下附件 | 打开 IM 或群 | 下次打开单据,对话不在现场 |
| 业务说明写在评论 | 审批人当意见读 | 轨迹里找不到「为什么用这辆车」 |
| 通过时顺手评论 | 只调了评论接口 | 任务还停在当前节点 |
| 批量通过想带一句 | 复用评论接口 | 评论不推动流程,意见又没落轨迹 |
一句话:备注说明业务,评论用来沟通,意见用来做结论。三套 API、三个落点,不要共用一个 textarea。
RuoYi Office 的办理页路径从待办点「办理」进入,BasicForm 上有单据信息 / 审批信息 / 流程图 / 流程评论四个 Tab,底栏有通过、拒绝、评论。下面按这几个能点的位置拆。
一、先给出可直接抽取的定义
1.1 单据备注
单据备注,是业务表单上的字段,随草稿保存、随提交进入流程变量或业务表,不是 Flowable 的 Task Comment。用车单上叫「备注」,也可能叫「说明」「申请说明」。发起人在「单据信息」里填写,审批人通常只读。
它回答的是:这张业务单据本身要交代什么。例如「去机场接客户,车辆已协调」。
1.2 流程评论
流程评论,是挂在流程实例上的沟通记录。发表时只addComment,不complete任务,不改流程状态。类型为评论枚举里的"0"。前端底栏按钮文案是「评论」,Tab 名是「流程评论」。
它回答的是:办理过程中谁对谁说了什么,下次打开还能看见。
1.3 审批意见
审批意见,是点通过、拒绝、退回、转办等结论操作时写入的reason,会进任务轨迹,并通过评论类型"1"/"2"等出现在同一条时间线上。节点可配置「意见是否必填」。拒绝在多数节点上按必填校验。评论区填了 500 字,也不算拒绝理由。
它回答的是:这个节点为什么过、为什么驳。
1.4 IM 为什么不能替代评论
IM 是顶栏会话,消息在聊天里,不绑定当前processInstanceId。适合拉群、语音、不在单据现场的人。审批现场要留痕,用流程评论。两者可以并存,不要做成「有 IM 就删评论 Tab」。
二、办理页你能看见三处入口
待办列表/bpm/task/todo点「办理」,进入带流程实例的详情。以用车申请单为例,抬头有单据编号、申请人、审批中状态。
▲ 用车申请单 OA101,单据信息里有「备注」;底栏「评论」和「通过 / 拒绝」分开;右侧 Tab 还能切到流程评论、审批信息
请对照这一屏上的三个位置:
| 位置 | 文案 | 写的是什么 |
|---|---|---|
| 单据信息 → 基本信息 | 备注 | 业务说明,跟草稿走 |
| 底栏按钮 | 评论 | 打开气泡,发表流程评论 |
| Tab | 流程评论 | 时间线,看历史沟通和系统记录 |
| Tab | 审批信息 | 进度、任务列表,意见在轨迹里 |
| 底栏按钮 | 通过 / 拒绝 | 结论 + 审批意见 |
列表页本身不承载这三套字,本文不再配待办表格图。要看效果,一律进办理详情。
三、底栏「评论」:发表但不往下走
底栏「评论」和「通过」并排,颜色却不同:通过是实心主按钮,评论是描边。点开后是气泡表单,字段只有「评论内容」,最多 500 字,必填。提交文案是「评论成功」,页面不关、任务还在。
前端核心顺序:
asyncfunctionhandleComment(){formLoading.value=true;try{awaitcommentFormRef.value.validate();constcontent=commentForm.message.trim();if(!content){message.warning('评论内容不能为空');return;}awaitcreateComment(runningTask.value.id,content);commentFormRef.value.resetFields();popOverVisible.value.comment=false;message.success('评论成功');window.dispatchEvent(newCustomEvent('bpm-comment-refresh'));}finally{formLoading.value=false;}}注意三点:
- 必须带当前运行中任务的
taskId。没有在办任务时这个按钮不应出现。 - 成功后派发
bpm-comment-refresh。流程评论 Tab 不必整页刷新,监听这个事件重新拉列表即可。 - 没有调用通过、拒绝、complete。这是和审批意见的产品边界。
接口:POST /bpm/comment/create,body 为{ taskId, message }。权限是「有任务更新权限,或当前用户是该任务参与人」。已办、抄送进来的人,只要是参与人,也可以留言——这是催办、补充说明的常见路径。
▲ 通过是结论,拒绝是结论,评论是沟通;抄送 / 转办 / 委派 / 加签是另一类操作,也不要写进备注字段
四、Tab「流程评论」:一条时间线多种颜色
Tab 在 BasicForm 里,和单据信息、审批信息、流程图并列。有流程实例且有流程状态才渲染,草稿未提交看不到这页。
列表组件按实例 id 拉取:GET /bpm/comment/list-by-process-instance-id。空态文案是「暂无评论」。有数据时每人一条:头像、昵称、字典标签、所属任务名、时间、正文。
真机里打开「流程评论」,即便谁都还没点过底栏「评论」,也可能已经有一条系统写入的通过记录。用车单 OA101 上能看到发起人节点自动通过的那句「审批通过,原因是:发起人节点首次自动通过」。这恰好说明:这个 Tab 是实例上的时间线,不是纯聊天室。
▲ 标题「流程评论 共 1 条」;记录带任务名「发起人」;正文是通过模板,不是底栏留言
类型不是前端写死的。字典bpm_comment_type决定标签文案和颜色。时间线左侧圆点取类型名的第一个字,例如「评」「通」「不」。
| type | 名字 | 典型来源 | 会不会结束任务 |
|---|---|---|---|
| 0 | 评论 | 底栏「评论」 | 否 |
| 1 | 审批通过 | 点通过,意见写入 format 模板 | 是(普通通过路径) |
| 2 | 不通过 | 点拒绝 | 是,流程走向拒绝 |
| 3 | 已取消 | 系统取消 | 流程结束 |
| 4 | 退回 | 退回上一节点 | 任务回到指定节点 |
| 5 / 6 | 委派发起 / 完成 | 委派来回 | 委派态变化 |
| 7 | 转派 | 转办 | 办理人变化 |
| 8 / 9 | 加签 / 减签 | 加签操作 | 审批人集合变化 |
| 10 | 撤回 | 发起人撤回 | 视流程配置 |
所以「流程评论」Tab 里看到的不全是人肉留言。通过、拒绝也会作为带类型的记录出现在同一条时间线。产品上仍要分开入口:人肉留言走底栏评论,结论走通过/拒绝。读者在 Tab 里能按颜色区分,不需要再开一张「系统日志」页。
后端创建人肉评论只有一行业务:
@Override@Transactional(rollbackFor=Exception.class)publicvoidcreateComment(BpmCommentCreateReqVOreqVO){Tasktask=bpmTaskService.validateTaskExists(reqVO.getTaskId());createComment(task.getId(),task.getProcessInstanceId(),BpmCommentTypeEnum.COMMENT,reqVO.getMessage());}@OverridepublicvoidcreateComment(StringtaskId,StringprocessInstanceId,BpmCommentTypeEnumtype,Object...params){taskService.addComment(taskId,processInstanceId,type.getType(),type.formatComment(params));}addComment是引擎提供的评论 API,不是complete。message 上限 500,空串直接校验失败。
五、审批意见:通过和拒绝才写 reason
点「通过」打开的气泡里,字段名是「审批意见」,校验读节点配置reasonRequire。为 true 时,空意见不能过,错误码语义是「审批意见不能为空」。为 false 时可以空着过,轨迹里意见可空。
点「拒绝」同样走意见框。很多流程会把拒绝配成必填,避免「手滑点红按钮、理由没有」。意见会按模板格式化,例如「审批不通过:原因是:{}」,再以类型"2"写入同一套 Comment。
通过路径里,意见和完成任务是绑在一起的:
BooleanreasonRequire=parseReasonRequire(bpmnModel,task.getTaskDefinitionKey());if(reasonRequire&&StrUtil.isEmpty(reqVO.getReason())){throwexception(TASK_REASON_REQUIRE);}updateTaskStatusAndReason(task.getId(),BpmTaskStatusEnum.APPROVE.getStatus(),reqVO.getReason());taskService.addComment(task.getId(),task.getProcessInstanceId(),BpmCommentTypeEnum.APPROVE.getType(),BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));Map<String,Object>variables=validateAndSetNextAssignees(task.getTaskDefinitionKey(),processVariables,bpmnModel,reqVO.getNextAssignees(),instance);runtimeService.setVariables(task.getProcessInstanceId(),variables);taskService.complete(task.getId(),variables,true);和人肉评论对比:
| 动作 | 写 Comment | complete 任务 | 改业务单据 |
|---|---|---|---|
| 底栏评论 | type=0,原文 | 否 | 否 |
| 通过 | type=1,带「审批通过,原因是」 | 是 | 仅当前节点允许改的字段 |
| 拒绝 | type=2 | 否(走拒绝流转) | 通常只读 |
| 改单据备注 | 否 | 否 | 是,写业务表 |
「审批信息」Tab 展示进度时间线和任务列表,意见出现在节点卡片和审批记录表里,而不是备注字段里。不要让实施把审批信息 Tab 理解成「第二个评论区」。
▲ 发起人已通过,当前停在部门负责人;审批记录里能看到「审批通过 / 审批中」,和流程评论 Tab 是同一批事实的两种排版
进度条回答「走到哪了」,记录表回答「谁在这个节点、状态是什么」,流程评论时间线回答「当场说过什么、系统记过什么」。三块看的都是这张单,盒子不能并成一个 textarea。
六、和批量通过、抄送、IM 的边界
上一篇讲过待办批量通过:预检不合格的节点不能勾,提交时同实例串行。批量通过可以带一句统一审批意见,但不会去调评论接口。需要沟通的单,请从批量里拿出去单条办理,用底栏评论问清楚再点通过。
抄送是另一颗底栏按钮:选人 + 可选抄送理由。抄送理由也不是流程评论,不会出现在评论时间线的 type=0 里。被抄送人打开单据,应能在审批信息或抄送记录里看到,而不是去评论区找。
转办、委派、加签各自有理由字段,对应类型 5~9。它们改变办理人集合,属于结论类操作的近亲,仍然不是「问一句油费谁出」。
IM:顶栏开聊会离开当前单据。适合把不在审批链上的人拉进来。谈完如果形成对单据有约束的结论,应回到评论或意见里落一行,否则审计只看得到聊天记录、看不到流程实例。
七、权限与参与人
评论列表:有bpm:task:query,或者当前用户是该流程实例参与人。发起人、已办、抄送都能回看,避免「只有当前办理人看得见沟通」。
发表评论:有bpm:task:update,或者是该任务参与人。这样已办的人仍可补充一句「发票已补传」,不必把任务抢回来。
不要把评论接口做成匿名留言板。taskId必须能校验到真实任务,防止拿别人的实例 id 灌水。
八、前端 Tab 为什么要拆四页
BasicForm 的 Tab 不是装饰:
| key | 名称 | 什么时候出现 | 内容 |
|---|---|---|---|
| 1 | 单据信息 | 始终 | 业务表单 + 明细 + 附件 |
| 2 | 审批信息 | 已有实例且有状态 | 进度时间线 + 任务列表 |
| 3 | 流程图 | 同上 | 简易查看器高亮当前节点 |
| 4 | 流程评论 | 同上 | 评论时间线 |
草稿未提交:没有实例,后三个 Tab 不出现,备注仍可写在单据信息里。这正好说明备注属于业务表单,不属于引擎 Comment。
四个 Tab 共用同一套页头(单据编号、申请人、状态)和同一套底栏。底栏按钮按任务状态显隐:运行中才有通过/拒绝/评论。已结束只留关闭、打印一类。
评论列表用窗口事件刷新,而不是 provide/inject 整树,是因为底栏和 Tab 不在同一个子树深处,事件更不容易漏。
列表组件自己按processInstanceId拉数。实例 id 变化(从待办 A 跳到待办 B)会watch重拉;组件挂载时监听bpm-comment-refresh,卸载时摘掉,避免开着多个详情页签时互相刷新错实例。空态用「暂无评论」,不要用表格的「暂无数据」——读者会以为附件表坏了。
每一行的结构固定,方便以后加「回复」也不用改时间线轴:
| 区块 | 数据来源 | 说明 |
|---|---|---|
| 左侧圆点字 | 字典 label 截第一字 | 「评 / 通 / 不」一眼分类型 |
| 圆点颜色 | 字典 cssClass 或 colorType | 与 DictTag 同一套色 |
| 头像昵称 | 用户接口按 comment.userId 拼接 | 系统记录也可能是发起人 |
| 字典标签 | bpm_comment_type | 不要前端写死中文 |
| 任务胶囊 | historic task name | 发起人自动通过会显示「发起人」 |
| 时间 | comment.createTime | 按统一日期时间格式 |
| 正文 | fullMessage | 预格式化空白,长文本换行 |
OA101 那条「审批通过,原因是:发起人节点首次自动通过」出现在这里,是因为通过路径写了 type=1。它不是底栏评论,也不是单据备注。把这张图给实施看,比口头解释「时间线会混系统记录」有效。
九、数据结构:用引擎 Comment,不另起业务表
人肉评论和通过/拒绝记录,都落在 Flowable 的 ACT_HI_COMMENT(或等价历史评论表)。字段够用:任务 id、实例 id、类型、全文、时间、用户 id。列表接口再拼用户昵称、头像、任务名。
不要为「流程评论」再新建业务评论表,除非已经确定要附件、@人、已读、回复树。当前产品是扁平时间线,500 字纯文本,引擎历史评论足够。多一张表就要考虑和 type=1/2 的轨迹如何去重,读者会问「为什么通过记录出现两次」。
列表接口会把fullMessage、时间、任务、用户拼成 VO。前端不要自己解析模板字符串去猜「这是通过还是留言」——用type和字典标签。发起人自动通过那条,正文带「审批通过,原因是:」,类型却不是 0,字典色也不同。
若节点开启签名,通过气泡里还会多一块签图片。签名属于结论操作的附件,不会写进评论内容字段。评论气泡只有一个 textarea,刻意不放上传,避免把沟通区做成第二个附件栏。
请求体很小:
| 字段 | 约束 | 说明 |
|---|---|---|
| taskId | 非空 | 当前运行任务 |
| message | 非空,最长 500 | 原文,不做模板包装 |
通过/拒绝的 reason 走任务接口,不走/bpm/comment/create。创建接口内部写死 type=评论,调用方不能自己传「我是通过」。这避免了前端拿评论接口伪造一条「审批通过」。
管理端两个接口放在/bpm/comment下,和任务通过/拒绝不在同一个 Controller,这是有意的:评论容量、审计、限流以后可以单独加,不必改审批主路径。
| 方法 | 路径 | 谁能调 | 做什么 |
|---|---|---|---|
| GET | /bpm/comment/list-by-process-instance-id | 任务查询权限或实例参与人 | 按实例拉全部类型的 Comment 并拼用户、任务名 |
| POST | /bpm/comment/create | 任务更新权限或该任务参与人 | 只写 type=0 |
列表接口不会在服务端按类型过滤。前端如果只想展示人肉留言,可以自己判断type === '0',但产品选择全量展示、用颜色区分。过滤会让发起人自动通过从时间线消失,实施会以为「发起节点没留痕」。
前端封装也很薄:createComment(taskId, message)只 POST 这两个字段;列表 GET 只要processInstanceId。不要在前端拼「审批通过,原因是:」——那是后端模板的事。
类型枚举和模板长这样,抄的时候不要把 type 改成整数——引擎 Comment 类型是字符串:
COMMENT("0","评论","{}"),APPROVE("1","审批通过","审批通过,原因是:{}"),REJECT("2","不通过","审批不通过:原因是:{}"),CANCEL("3","已取消","系统自动取消,原因是:{}"),RETURN("4","退回","任务被退回,原因是:{}"),TRANSFER("7","转派","[{}]将任务转派给[{}],转派理由为:{}"),WITHDRAW("10","撤回","任务被撤回,原因是:{}");人肉评论的 format 是"{}",所以正文就是用户原话。通过记录会带前缀「审批通过,原因是:」,和底栏留言一眼能分开。流程评论 Tab 里那条发起人自动通过,走的就是 APPROVE 模板,不是有人在气泡里打了这些字。
十、设计对照
| 决策点 | 错法 | 本方案 | 理由 |
|---|---|---|---|
| 问一句材料 | 点拒绝 | 底栏评论 | 不中断流程 |
| 业务说明 | 写进评论 | 单据备注 | 草稿阶段就能存 |
| 为什么驳 | 只发评论 | 拒绝 + reason | 轨迹和状态一致 |
| 沟通是否离开单据 | 只用 IM | 评论留在实例上 | 刷新还在 |
| 评论是否推动节点 | 调用 complete | 只 addComment | 职责单一 |
| 类型 | 全当普通留言 | 字典 0~10 | 时间线可着色 |
| 批量通过 | 循环发评论 | 统一意见走 approve | 评论不推动流程 |
十一、技术亮点
| 设计要点 | 实现方式 | 价值 |
|---|---|---|
| 入口分离 | Tab + 底栏按钮 + 表单字段 | 用户不会点错盒子 |
| 评论不 complete | TaskService.addComment | 沟通与结论解耦 |
| 结论也进时间线 | 通过/拒绝写 type=1/2 | 一个 Tab 看完沟通和结论 |
| 意见可配必填 | 节点扩展属性 reasonRequire | 关键节点不能空过 |
| 500 字上限 | VO@Size(max=500)+ 输入框计数 | 防止把附件正文贴进来 |
| 参与人可读可写 | 权限 or 参与人表达式 | 已办仍能补充说明 |
| 刷新事件 | bpm-comment-refresh | Tab 不必整页重载 |
| 批量不发评论 | 批量只走 approve | 和资格预检那套一致 |
十二、FAQ
Q1:评论里写了「不同意」,流程会驳回吗?
不会。驳回必须点「拒绝」并填写审批意见。评论只留痕。
Q2:发起人还没提交,能不能评论?
没有流程实例就没有评论 Tab。业务说明写在单据备注里,保存草稿即可。
Q3:审批意见必填的节点,能否用评论凑数?
不能。通过接口读的是reason,不是最新一条 type=0。节点配了必填,空意见会直接失败。
Q4:为什么通过记录也会出现在流程评论 Tab?
同一套引擎 Comment。用字典颜色区分「评」和「通」,避免再做一张系统日志。入口仍然分开。
Q5:批量通过那句统一意见算评论吗?
算审批意见,走批量通过接口,不走/bpm/comment/create。需要先问清再过的单,不要放进批量。
Q6:移动端只有一个输入框怎么办?
应对齐 PC:备注在表单字段,评论在详情底栏或独立面板,通过/拒绝弹意见。不要把 App 做成「一个聊天框代替审批」。
Q7:发起人自动通过为什么出现在评论里?
发起人节点首次自动通过会写一条 type=1 的评论,模板是「审批通过,原因是:{}」。它不是有人点了底栏评论。时间线要包含系统结论,审计才完整。
Q8:已结束的流程还能评论吗?
底栏评论按钮跟着运行中任务走,结束后通常只留关闭、打印。历史沟通仍可在流程评论 Tab 只读回看。不要在结束后再开放留言,除非产品明确要「归档后答疑」。
Q9:流程图 Tab 和评论有关系吗?
没有。流程图只看节点走到哪。不要把评论气泡画到 BPMN 图上,手机上点不准,PC 上也和「审批信息」进度条重复。
Q10:抄送理由会进流程评论吗?
不会。抄送是独立按钮和独立记录。被抄送人打开单据,应在审批信息或抄送区看理由。需要全员可见的说明,请再发一条流程评论。
十三、快速体验
在线演示:https://ruoyioffice.com/web/(账号 admin / admin123)
建议路径:
- 流程中心 → 待办任务 → 任选一条点「办理」(例如用车申请单)
- 在「单据信息」找到备注字段,确认它和底栏不是同一个输入框
- 点底栏「评论」,写一句不影响结论的话,提交后任务仍在
- 打开「流程评论」Tab:即便还没人肉留言,也可能看到发起人自动通过那条「审批通过,原因是:…」
- 点底栏「评论」再发一条,时间线应变成两条,颜色标签不同
- 打开「审批信息」Tab,看进度条和审批记录表,对照评论 Tab 是同一批事实的另一种排版
- 点「拒绝」看意见框(可取消,不必真驳),对比评论气泡只有内容、没有「驳回」语义
- (可选)待办列表试批量通过:弹窗里是统一审批意见,不会出现评论时间线入口
配节点时记得打开流程设计器,找到「审批意见是否必填」。关键财务节点建议打开;会签里随便问一句的节点可以关掉,避免人人都要编一句「同意」才能过。必填校验的是通过/拒绝的reason,与评论内容无关。
简易设计器和 BPMN 设计器都要能改到这个开关,不要只做在其中一套。节点 JSON 里对应扩展属性,列表预检批量通过时也会读:意见必填的节点不能勾批量。这和「评论区有没有字」仍然无关——批量看的是 reasonRequire,不是最新一条 type=0。
源码仓库:GitHub:https://github.com/yuqing2026/ruoyi-office | GitCode:https://gitcode.com/zhouzhongyan/ruoyi-office | Gitee:https://gitee.com/yqzy1688/ruoyi-office
结语
审批现场最贵的不是再做一个输入框,而是让「问一句」和「驳回」抢同一个提交按钮。备注跟业务草稿走,评论跟流程实例走且不 complete,意见跟通过/拒绝走并改状态。IM 留给不在链上的人。把这三个盒子配清楚,待办积压时才敢让领导先评论、后结论,而不至于误驳一单用车。
同一套拆分还可以用在报销补票、用印问章面、合同问条款:沟通先留在实例上,条款变更再走退回或拒绝。
现场把三个盒子配错,通常是这五种,可以对着办理页逐项排除:
| 现场说法 | 实际点了哪 | 应该怎么做 |
|---|---|---|
| 「我评论了不同意,单怎么还在?」 | 底栏评论 | 要驳回请点拒绝 |
| 「备注里写了发票号,审批人说没看见」 | 单据备注,审批人看的是评论 Tab | 补一句流程评论,或把发票放到附件 |
| 「通过必须填意见太烦」 | 节点打开了意见必填 | 非关键节点在设计器关掉 reasonRequire |
| 「群里说了可以过,系统没记录」 | 只用了 IM | 回到底栏评论或通过意见落一行 |
| 「批量过了但评论区没有」 | 批量通过只写审批意见 | 符合预期;要沟通请单条办理 |
配置侧再记两个开关:节点「审批意见是否必填」、底栏按钮显隐(运行中任务才有评论)。不要把评论按钮配进已结束的查看页,避免归档单据被灌水。
待办列表、站内信、企微通知里,摘要应继续用单据编号、节点名、发起人,不要截评论最后一句当待办标题。评论可能是「油费谁出」,出现在待办卡片上会像催办恐吓。待办字段怎么留,和评论不是同一件事。
打印审批单时,常见需求是把审批意见打进表格,而不是把全部 type=0 留言打进去。意见在审批记录里,留言在评论 Tab。套打模板应绑定「审批意见」字段;若客户坚持「沟通也要进附件 PDF」,做成可选页,默认关闭。
移动端对齐时,底栏仍应分开「通过 / 拒绝 / 评论」。小屏不要把三个入口收进同一个 ActionSheet 还共用一个输入框。PC 上已经证明分盒能减少误驳;App 上更需要分盒,因为误触面积更大。
💡想要体验 RuoYi Office 的强大功能?
🌐在线演示:https://ruoyioffice.com/web/(账号 admin / admin123)
📦源码仓库:GitHub:https://github.com/yuqing2026/ruoyi-office | GitCode:https://gitcode.com/zhouzhongyan/ruoyi-office | Gitee:https://gitee.com/yqzy1688/ruoyi-office
💬技术咨询:添加微信17156169080,备注「RuoYi Office」
⭐如果觉得不错,请给个 Star 支持一下!