企业办公审批小程序实战:动态表单与审批流配置指南

发布时间:2026/10/18 18:52:40
企业办公审批小程序实战:动态表单与审批流配置指南 简介这是一套面向企业、机关单位的办公审批小程序完整源码包覆盖请假、报销、外出、合同、采购、入职等多种审批流程内置管理员、员工、审批人员三类角色审批结果可通过微信消息提醒实时送达申请者适用于快速搭建移动办公审批场景。资源共489个文件压缩包约2.12MB以189个js逻辑脚本、103个wxss样式、85个wxml页面、78个json配置为主并配有png/jpg/gif图标素材、docx安装使用手册、xlsx表格及md说明文档目录结构清晰便于定位和二次开发。当前已有47人学习下载包内含二维码生成库、图表库、数据库工具封装和页面辅助脚本可直接参考消息通知配置、角色权限划分与审批流程跳转逻辑也适合作为企业微信小程序开发的学习范例。1. 企业办公审批小程序当请假还在满楼找签字流程已经可以这样跑你是那个三天两头请假、报销的人就一定见过这样的画面申请单打印出来跑两层楼找主管签字人不在就贴在工位上干等月底报销单攒了一摞最后卡在“老板出差”这一环合同要会签采购要走部门经理再到财务每一步都是“在途”没人知道卡在哪。办公审批小程序要解决的就是这一类脏活——把请假审批、报销审批、外出审批、合同审批、采购审批、入离职审批这些规则固化成一套可配置的流程让发起、审批、抄送、归档都在手机里一次完成让流程跑到人能看见的地方不给“在途”留黑匣子。它的价值不在“无纸化”这三个字而在于每一单当前在哪个节点、谁在办、多久没办随时能查。这套东西适合没有专职流程系统的小公司、多层级企业以及需要审批留痕的机关部门也适合已经有核心系统、想单独把审批外挂出去的团队。下面按“数据模型 → 流程配置 → 小程序端 → 避坑 → 进阶”的顺序给你一条能落地的路径。2. 审批数据模型四张表加一张 JSON 表单撑起整个审批流2.1 为什么要拆成“模板-实例-任务”三层而不是一个表单走天下请假和报销最大的区别不是字段不一样而是流程不一样请假过了直属主管基本就结束报销过了主管还得再走财务合同还要法务会签。如果每个审批类型单独建一套表和接口每加一个场景就改一遍代码撑不到第五个就得返工。把审批抽象成“模板-实例-任务”三层之后新增一个审批类型只是往模板里加一条配置代码一行不用动。模板负责描述“长什么样、怎么流转”实例是用户发起后生成的一张具体单据任务则是这张单据上待处理的一个个节点。表单字段、节点规则全部跟着模板走实例存实际填写的数据和当前状态任务记录每个操作人的处理结果和时间这样设计才能应对请假、报销、外出、合同、采购这些流程结构差异很大的场景。我在做这类系统时一般还会加一层“表单数据”与“流程数据”的分开存储表单数据放在实例的 JSON 字段里流程的走向由模板里的规则计算出来这样新增一个审批项时不用跑数据库迁移。好处是上手快代价是 JSON 字段无法直接 SQL 聚合统计所以统计报表要走单独的归档表或用脚本定期导出。2.2 最小建表模板、实例、任务根上的“两个状态字段”下面这套建表语句是常见做法里最精简的一套实际项目里我都会在此基础上扩展核心结构先立住再往上加字段CREATE TABLE dept ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, parent_id BIGINT DEFAULT 0, manager_id BIGINT DEFAULT NULL, status TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, dept_id BIGINT NOT NULL, job_title VARCHAR(50) DEFAULT , open_id VARCHAR(64) DEFAULT , status TINYINT DEFAULT 1 ); CREATE TABLE approval_template ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(32) NOT NULL, name VARCHAR(50) NOT NULL, form_schema JSON NOT NULL, flow_config JSON NOT NULL, status TINYINT DEFAULT 1, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE approval_instance ( id BIGINT PRIMARY KEY AUTO_INCREMENT, template_code VARCHAR(32) NOT NULL, title VARCHAR(200) NOT NULL, applicant_id BIGINT NOT NULL, form_data JSON NOT NULL, current_node VARCHAR(50) NOT NULL, status TINYINT NOT NULL DEFAULT 0, version INT NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE approval_task ( id BIGINT PRIMARY KEY AUTO_INCREMENT, instance_id BIGINT NOT NULL, node_key VARCHAR(50) NOT NULL, node_name VARCHAR(50) NOT NULL, assignee_ids VARCHAR(200) NOT NULL, assignee_names VARCHAR(500) NOT NULL, status TINYINT NOT NULL DEFAULT 0, comment TEXT, action_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );这里有两个字段一定要刻意保留approval_instance.version用于乐观锁处理并发审批approval_task里的assignee_ids和assignee_names在创建任务时就固化下来写清是谁。即使后来组织架构调整、人员离职历史单据依然能看到当时审批人是谁这是后面避坑章里最值钱的一条经验。form_schema存放表单定义flow_config存放节点流程定义两者都是 JSON。approval_instance.status建议统一定义为 0 审批中、1 通过、2 驳回、3 撤回避免不同人写不同数值。2.3 用 JSON Schema 描述表单别为每个审批类型开发一个页面表单的动态化是“多场景复用”的关键一步。用一套 JSON 结构描述请假、报销、采购、合同的表单字段前端渲染引擎就可以复用同一套页面。{ fields: [ { key: leave_type, label: 请假类别, type: select, options: [事假, 病假, 年假], required: true }, { key: start_time, label: 开始时间, type: datetime, required: true }, { key: end_time, label: 结束时间, type: datetime, required: true }, { key: total_hours, label: 请假时长(小时), type: number, required: true }, { key: reason, label: 事由说明, type: textarea, maxLength: 500 }, { key: attachment, label: 附件, type: file, maxSize: 10, unit: MB } ] }字段类型我一般只保留六种input、number、textarea、select、datetime、file足够覆盖办公审批的绝大多数场景。select 的options可以是字符串数组也可以扩展成{label, value}结构。maxSize的单位放在配置里而不是写死在代码中因为不同场景对附件大小的容忍度不一样报销凭证可能只要 2MB 以内合同扫描件却需要 20MB。与表单 JSON 配套的一定是服务端校验。前端校验只是体验服务端要重新走一遍必填检查、枚举值合法性、数字范围、附件大小。否则有人直接调接口塞一个total_hours: -8进来后续条件分支计算就会出问题。2.4 创建实例接口服务端校验与事务边界创建审批实例的逻辑是整套系统的第一个要塞。用 Flask 风格的接口做演示app.post(/api/v1/approval/instances) def create_instance(payload: dict): current_user get_current_user(request) # 从登录态取用户绝不信任前端传的 userId template_code payload[templateCode] form_data payload[formData] template get_template(template_code) if template is None or template[status] ! 1: raise BizError(审批模板不存在或已停用) validate_form(template.form_schema, form_data) # 服务端必填、类型、大小校验 with db.transaction(): instance_id insert_instance( template_codetemplate_code, titlebuild_title(template, form_data), applicant_idcurrent_user.id, form_dataform_data, current_nodetemplate.flow_config[start][next] ) create_task_for_node(instance_id, template.flow_config[start][next], template.flow_config) notify_assignees(instance_id, template.flow_config[start][next]) return {instanceId: instance_id}get_current_user必须从服务端 session 或 token 里解析用户身份前端传什么userId都不可信接口层要做一层防越权。build_title的作用是用模板配置拼出单据标题比如张三的请假申请 2025-06-01这样审批列表里一眼就能看出是什么单子不用展开详情。notify_assignees在事务提交后再调避免消息发出去了但数据库回滚导致“收到了通知却查不到单子”的尴尬情况。事务边界只包数据库写操作外部调用全部放在事务外这个顺序是血泪经验。3. 审批人与节点配置把“找谁签”变成规则计算覆盖五种指派方式3.1 审批人规则配置自选、指定人、部门主管、角色、发起人本人审批节点里最核心的是assignee配置。常见做法是支持五种规则applicant_self发起人本人、dept_manager部门主管、role指定角色、user指定成员、applicant_designated发起人自选。请假和外出多用部门主管报销加一个财务角色节点采购可能还要指定采购部门负责人。{ key: direct_manager, type: approval, name: 直属主管审批, assignee: { rule: dept_manager, managerLevel: 1 }, mode: sequence, next: finance }managerLevel: 1表示直属主管2 表示再上一级。这个参数不要写死在代码里因为不同公司的汇报层级不一样有的部门主管就是经理有的还要先过组长再过经理。规则解析器只负责“按层级往上找”层级由模板配置决定。部门主管的判定依赖组织架构里的dept.manager_id这是最直接的一种设计。如果公司有“代理主管”或“临时负责人”这类需求可以在 dept 表加一个deputy_manager_id字段解析规则时按优先级取。3.2 主管是怎么算出来的组织架构表与汇报关系组织架构表谁都会建但有一个坑很隐蔽审批发生时必须“按当时的组织架构”找人而不是按现在的。员工调岗后历史审批单不能跟着变审批人否则旧单子审批权限会漂移。我一般会要求组织架构表增加effective_date生效时间用户表增加dept_id当前部门的同时在实例表里固化applicant_id和审批人快照。解析主管的伪代码如下def resolve_dept_manager(user, level1): current_dept get_user_dept(user[id]) manager get_dept_manager(current_dept) for _ in range(level - 1): if manager is None: return None manager get_dept_manager(manager[dept_id]) return manager这里每一步都要判空。实际交付时经常遇到某个小部门负责人离职后没维护manager_id流程走到这个节点直接报错或者把单子卡死。我通常会在解析不到主管时走兜底规则往上找上级部门的主管再找不到就转给租户管理员并在任务记录里打一个标记方便排查。3.3 条件分支报销金额、请假时长决定下一节点有了规则解析还不够审批流需要能在节点之间做分支。最常见的是按金额或时长分线比如报销金额 1000 元以内部门主管直接批超过 1000 元要加签总经理。条件分支节点的配置长这样{ key: amount_branch, type: condition, name: 金额分支, nextByCondition: [ { when: [{ field: amount, op: lte, value: 1000 }], next: dept_manager }, { when: [{ field: amount, op: gt, value: 1000 }], next: general_manager } ], otherwise: dept_manager }条件表达式我用极简的三元组字段、操作符、值。操作符只需要eq、ne、gt、gte、lt、lte、contains七种足够覆盖权限、金额、时长、类别的判断。多个条件同时满足用and组合极少需要or真遇到了就拆成两个条件节点。这里要提醒一个细节条件里引用的字段名是表单 JSON 里的key不是中文标签所以key的命名必须稳定。比如报销金额叫amount不要叫money_total因为一旦表单发布后有历史数据改 key 就得写数据迁移脚本。3.4 会签、或签、逐级审批三种模式的选择边界审批节点除了“谁签”还要定“怎么签”。常见的是三种模式sequence逐级审批、countersign会签、or或签。逐级审批是 A 签完 B 才能签会签是所有人都要签且不分先后或签是其中一个人签了就行。选型上没有银弹只有匹配场景。报销、请假这类高频低风险的用逐级审批合同、采购这类需要多人把关的用会签而“部门主管或分管领导任一审批即可”这种场景用或签。 配置差别只在一个字段{ key: countersign_node, type: approval, mode: countersign, assignee: { rule: role, roleCode: finance } }会签模式下实现要格外小心同名节点会生成多条任务记录每条对应一个审批人。只有全部任务处理完才能流转到下一节点任意一个驳回整条流程回到发起人。这里最容易出现的 bug 是“部分人签了、部分人没签流程却提前入下一节点”原因是实现时把每个任务独立去推进了流程。正确做法是每个任务处理完先更新任务状态再统计该节点下所有任务是否全部结束全部结束才调advance_workflow。4. 小程序端落地动态表单、状态控制与消息通知三件事4.1 小程序页面骨架工作台、待办、已办、我的申请小程序端不用追求页面多华丽四个页面就能跑通全部核心场景工作台、待办、已办和我的申请。工作台展示可发起的审批类型入口待办是当前用户要处理的单子已办是自己处理过的我的申请是过去发起过的所有单据及当前状态。 发起审批的入口从模板表读而不是写死在前端这样才能做到“新增审批类型不改小程序发版”。页面之间传参不要只传instanceId还要带上单子的status详情页根据状态决定展示哪些操作按钮。比如一张已经结束的单子再进入详情页就不应该渲染“通过/驳回”按钮否则用户点了才会报错体验很糟。统一请求封装是前端的第一步所有接口都走同一个通道const request ({ url, method POST, data {} }) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, method, data, header: { Authorization: Bearer ${getToken()} }, success: (res) { if (res.data.code 0) resolve(res.data.data) else reject(new Error(res.data.message)) }, fail: reject }) }) }登录态通过 token 放在 header 里小程序端不要存明文用户信息。token 过期时统一拦截跳登录页不然每次接口报错都很莫名其妙。4.2 动态表单渲染与前端校验动态表单渲染说白了就是把服务端返回的formSchema.fields循环渲染出来。WXML 用wx:if按type分发到不同控件block wx:for{{formFields}} wx:keykey view classform-item text classlabel{{item.label}}text wx:if{{item.required}} classrequired*/text/text input wx:if{{item.type input}} value{{item.value}}>const canApprove task.assigneeIds.includes(currentUser.id) task.status 0 instance.status 0; const canRevoke instance.applicantId currentUser.id instance.status 0 taskList.every(t t.status 0) currentNode.type ! condition;这里的canApprove必须同时满足三个条件我是任务处理人、任务还在待办、实例还在审批中。缺少任何一个说明这个按钮不该显示。canRevoke的意思是发起人只在没有任何人处理过的情况下才能撤回一旦有人签过字就不再允许单方面撤回。实际前端的判断只是一层交互保护最终还是要由后端接口做同样的校验两端逻辑要保持一致避免“按钮显示了但接口拒绝”的尴尬。4.4 订阅消息与超时催办通知参数怎么设小程序订阅消息的特点是“用户授权一次只能推送一次”这是刚入坑时最容易踩的预期差。常见做法是在发起审批成功后引导发起人订阅消息这样审批人处理时能给发起人发一条结果通知审批人那边则要在待办列表页进入时做订阅引导这样下一单处理结果才能触达。wx.requestSubscribeMessage({ tmplIds: [template_id_xxx], success(res) { if (res[template_id_xxx] accept) { // 用户同意订阅后端可推送一次结果通知 } } })后端调用订阅消息发送接口时要传touser用户 openid、template_id、page、data四个核心参数。page最好指到详情页并带上 instanceId用户点开直达单子不要只进首页。超时催办用定时任务扫描待办。参数上我会设置两个超时阈值和最大提醒次数。阈值按业务分请假审批 4 小时就算超时合同审批给 24 小时提醒次数默认 3 次避免一天催五次被人投诉。定时任务里要记录last_remind_at或remind_count任务断点重跑也不会重复打扰。5. 避坑指南审批小程序最容易翻车的五个细节5.1 越权查看光校验登录绕过数据权限全部单子泄露现象一个普通员工能通过改接口参数看到别人的请假单、报销金额甚至包括总经理的审批记录。原因接口只校验了“是否登录”没有校验“是否有权限看这张单”。我在实际交付中见过只按 instanceId 查询、完全不判断当前用户是不是申请人或审批人的实现。解决所有查询类的接口在返回前必须做数据范围校验。规则是申请人本人、当前待办或已办的审批人、抄送人、以及具备管理权限的角色才能看这张单。后端统一封装一个checkInstanceVisible(instance, currentUser)所有查询入口都过一遍没有例外。5.2 并发双击重复提交把实例状态改坏现象两个审批人同时点了“通过”结果实例被推进两次或者同一个任务被处理了两次流程出现两条后续分支。原因审批任务处理没有做并发控制两个请求同时读到任务状态都是待办都继续执行了。解决更新任务状态时带上乐观锁用status 0作为更新条件更新成功影响行数为 0 就说明已被处理。UPDATE approval_task SET status 1, action_at NOW(), comment %s WHERE id %s AND status 0发布到生产环境前一定要用两个账号或两个浏览器同时对同一任务点“通过”做一次联调很多事故只有并发触发时才暴露出来。5.3 日期少一天与金额差一分数据库精度玄学现象用户在前端选了 6 月 1 日审批详情里却显示 5 月 31 日报销金额明明填的是 1234.56数据库里却存成了 1234.559999。原因日期字段在前后端传输时发生了时区转义MySQL 的DATETIME不带时区而 JDBC 连接配置的serverTimezone可能与服务器不一致金额用了FLOAT或DOUBLE浮点存储天然有误差。解决日期字段统一定义为字符串格式YYYY-MM-DD或YYYY-MM-DD HH:mm:ss直接入库。金额用整数分存储前端输入元、后端转换成分、展示再转回元或者在数据库里用DECIMAL(14,2)并在所有计算里统一用定点数。关键点是“展示层格式化”不要靠数据库隐式转换。5.4 附件垃圾临时文件未转正与存储膨胀现象用户发起审批时上传了 20MB 附件最后把申请撤回了或者根本没提交成功OSS 或服务器存储里却残留了大量文件一个月多占几个 GB。原因上传接口没有和审批实例创建接口打通文件上传成功就认为是永久文件没有“临时上传 → 审批通过后转正 → 被丢弃后清理”的机制。解决上传接口统一写入临时目录或临时对象存储路径只有审批实例创建成功后才把附件标记为正式文件同时提供一个定时清理任务把超过 24 小时未被引用的临时文件删掉。给文件命名设计统一规则时带上instanceId或applicantId前缀排查问题时能直接定位到是谁传的。5.5 组织调整后旧单子“查无此人”和历史审批链断掉现象某个员工三个月前提交的报销单现在想看历史审批记录结果审批人姓名显示为空或者因为部门被合并导致主管解析失败老单子的流程时间线断了一截。原因实例和任务只存了用户 ID没有存姓名和部门快照用户表和部门表的数据一变历史数据跟着失效。解决在approval_task表固化assignee_names在approval_instance表固化申请人姓名、部门名称和当时的表单数据。职位调整、部门合并都是常态唯一可靠的做法是“审批发生时把要展示的信息都拍一张快照存下来”。6. 进阶把审批底座做成可复用能力附全套验收清单一套审批系统真正能用起来不是写完审批流就算结束而是要能响应“再帮我加一个审批类型”这句话。我一般会在模板管理后台提供配置界面至少包括表单字段配置、节点顺序配置、审批人规则选择、条件分支设置四块内容。支持模板导入导出这样公司有多个部门、甚至多个单位时可以直接复制模板再改字段不需要从零配置。进一步可以开放 OpenAPI让其他系统按约定格式调用发起接口例如财务系统里直接发起一笔报销审批。做到这一步审批就从“一个功能”变成了“一个可复用的底座”。上线前的验收清单我反复用过很多轮每一轮都能筛出问题这里直接整理给你验收项目关键检查点模板配置请假、报销、采购、合同各建一套改模板字段后新建单据立即生效节点流转条件分支按金额/时长正确路由驳回后回到发起人可修改再提交审批人解析主管缺失时走兜底规则自选审批人只能选组织架构内成员数据权限非相关人员看不到他人单据审批人只能看到与自己相关的流程消息通知订阅消息在合理时间内送达超时催办按阈值触发且不重复打扰并发处理两个终端同时操作同一任务只有一个成功另一个提示已处理历史归档组织架构调整后历史单据的审批人姓名和部门仍完整显示这套底线全部跑通之后再放量推广。我自己的习惯是让后勤同事拿真实场景各发一单请假、报销、外出和合同各跑一遍全链路从发起、审批、驳回、重提、撤回全走一遍确认没有一处卡壳才开始培训其他人。审批系统最怕的是上线第一天就在众目睽睽之下出 bug那之后再去解释“这是并发问题”已经没有意义。代码结构可以逐步优化但流程正确性必须在上线前钉死。边界清楚了、参数可配了、历史不漂移了这个审批小程序才真正算交付完成。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询