企业字段权限设计:不同审批节点字段编辑、显隐控制落地
企业字段权限设计:不同审批节点字段编辑、显隐控制落地
🌐演示地址:http://ruoyioffice.com | 📦源码1:ruoyi-office-vben | 📦源码2:ruoyi-office | 📦源码3:ruoyi-office | 💬微信:17156169080(备注「RuoYi Office」)
功能权限管「能不能点按钮」,数据权限管「能看到哪些行」——但很多审批场景还要第三层:同一张单据,不同节点的人看到不同字段。例如请假单:员工填原因和天数,HR 节点才显示「年假扣减」,财务节点才显示「薪资影响」;敏感字段对其他人应NONE 隐藏。RuoYi Office 在 BPM 模块实现了列级字段权限:
BpmFieldPermissionEnum四种权限 + 流程设计器按节点配置 +useBusinessFieldPermission驱动basic-form.vue动态 schema。本文基于真实源码讲清设计与落地。
▲ 字段权限全景:READ/WRITE/NONE/REQUIRED 四种权限、行级数据权限 vs 列级字段权限、主表+明细表存储与 basic-form 运行时链路;HTML 源文件见images/field-permission-architecture.html
引言:为什么「行级权限」还不够?
企业审批表单有三类常见诉求:
诉求一:节点差异化填报。发起人只填基础信息,部门经理填「审批意见」,总经理才看得到「预算金额」。
诉求二:敏感字段脱敏/隐藏。薪资、身份证号、合同金额——不是简单把接口禁掉,而是表单上就不渲染(NONE),减少泄露面。
诉求三:明细行按节点放开。报销单主表只读,但财务节点允许编辑明细里的「税额」列。
| 权限类型 | 控制粒度 | 典型实现 | 回答的问题 |
|---|---|---|---|
| 功能权限 | 接口/按钮 | @PreAuthorize | 能不能提交? |
| 数据权限 | 行 | DataPermissionInterceptor | 列表能看到哪些单据? |
| 字段权限 | 列 | useBusinessFieldPermission | 这张单里哪些字段可见/可改? |
字段权限与数据权限正交:用户可能通过行级权限看到一条请假单,但在「部门经理审批」节点只能改approveRemark,其余字段 READ。
一、四种权限:BpmFieldPermissionEnum
后端枚举定义在yudao-module-bpm-api:
@Getter@AllArgsConstructorpublicenumBpmFieldPermissionEnum{READ(1,"只读"),WRITE(2,"可编辑"),NONE(3,"隐藏"),REQUIRED(4,"必填");privatefinalIntegerpermission;privatefinalStringname;publicstaticBpmFieldPermissionEnumvalueOf(Integerpermission){returnArrayUtil.firstMatch(item->item.getPermission().equals(permission),values());}}1.1 语义与 UI 行为
| permission | 枚举 | 制单态(流程未提交/可编辑) | 审批态(当前待办) | 已办/抄送 |
|---|---|---|---|---|
| 1 | READ | 可编辑(无配置时) | 显示,disabled | 显示,disabled |
| 2 | WRITE | 可编辑 | 显示,可输入 | 只读 |
| 3 | NONE | 隐藏 | 隐藏 | 隐藏 |
| 4 | REQUIRED | 可编辑 + 必填 | 可编辑 + 必填校验 | 只读 |
实体一致:
NONE在任何状态都隐藏,包括制单态——用于彻底不可见的敏感字段。
1.2 与前端常量对齐
前端@vben/constants中BpmFieldPermissionType与后端 permission 整型一一对应,避免魔法字符串散落。
二、配置存储:BpmFormFieldPermissionDO
流程设计器保存的配置落在表bpm_form_field_permission:
@TableName("bpm_form_field_permission")publicclassBpmFormFieldPermissionDOextendsBaseDO{@TableIdprivateLongid;/** 流程模型 ID,关联 Flowable ACT_RE_MODEL.ID_ */privateStringmodelId;/** BPMN 节点 activityId */privateStringnodeId;/** 关联 infra_codegen_table.id */privateLongcodegenTableId;/** 0=主表 1=明细表 */privateIntegertableType;/** Java 属性名,对应 codegen_column.java_field */privateStringfieldName;privateStringfieldLabel;/** 枚举 BpmFieldPermissionEnum */privateIntegerpermission;privateIntegersort;}2.1 主表 vs 明细表
| tableType | 含义 | 前端数据结构 |
|---|---|---|
| 0 | 主表字段 | BusinessFieldPermission.masterFields[fieldName] |
| 1 | 明细表字段 | detailFields[tableName][fieldName] |
明细表tableName与代码生成业务名或 VxeTable 表标识一致,便于getDetailColumnVisible控制列显示。
三、运行时模型:BusinessFieldPermission
审批详情接口会把当前节点解析后的权限下发给前端:
exportinterfaceBusinessFieldPermission{masterFields:Record<string,string>;detailFields?:Record<string,Record<string,string>>;}值域为read/write/none/required字符串(与BpmFieldPermissionType常量一致)。
3.1 三种视图状态
useBusinessFieldPermission的核心分支(注释即设计文档):
/** * 1. 无权限配置 → 回退到原有 computeBusinessFormReadonly 逻辑 * 2. 制单态(viewType 为 my 且流程可编辑状态)→ 全部可编辑 * 3. 审批态(isApproval === true)→ 按节点配置的权限应用 * 4. 已办/抄送态 → 全部只读 */exportfunctionuseBusinessFieldPermission(options:UseBusinessFieldPermissionOptions,):FieldPermissionResult{const{permission,isApproval,viewType,processStatus}=options;// ...}isInEditableState()判断制单态:非done/copy视图、非审批中、且流程状态属于可编辑集合(如草稿、驳回)。
四、useBusinessFieldPermission 核心判断
4.1 隐藏与只读
functionisFieldHidden(fieldName:string,tableName?:string):boolean{if(!hasPermissionConfig.value)returnfalse;constperm=getFieldPermission(fieldName,tableName);if(!perm)returnfalse;returnperm===BpmFieldPermissionType.NONE;}functionisFieldReadonly(fieldName:string,tableName?:string):boolean{if(!hasPermissionConfig.value){constvt=unref(viewType);if(vt&&BpmReadonlyViewTypes.has(vt))returntrue;if(unref(isApproval)===true)returntrue;return!BpmProcessInstanceStatusEditValue.includes(processStatusNumber.valueasnumber,);}if(isInEditableState())returnfalse;if(unref(viewType)&&BpmReadonlyViewTypes.has(unref(viewType)!))returntrue;if(unref(isApproval)!==true)returntrue;constperm=getFieldPermission(fieldName,tableName);if(!perm)returntrue;returnperm===BpmFieldPermissionType.READ;}关键设计:非审批态且流程不可编辑时,整单只读,不再沿用发起节点的 WRITE 配置——避免审批通过后申请人仍能改金额。
4.2 可编辑与必填
functionisFieldEditable(fieldName:string,tableName?:string):boolean{if(!hasPermissionConfig.value){returnisInEditableState();}if(isInEditableState())returntrue;if(unref(isApproval)!==true)returnfalse;constperm=getFieldPermission(fieldName,tableName);return(perm===BpmFieldPermissionType.WRITE||perm===BpmFieldPermissionType.REQUIRED);}functionisFieldRequired(fieldName:string,tableName?:string):boolean{if(!hasPermissionConfig.value)returnfalse;constperm=getFieldPermission(fieldName,tableName);if(perm!==BpmFieldPermissionType.REQUIRED)returnfalse;if(isInEditableState())returntrue;returnunref(isApproval)===true;}4.3 明细表 VxeTable 接入
functiongetDetailColumnVisible(fieldName:string,tableName:string):boolean{return!isFieldHidden(fieldName,tableName);}functionisDetailColumnEditable(fieldName:string,tableName:string):boolean{returnisFieldEditable(fieldName,tableName);}列表页明细 Grid 对column.field调用上述方法,即可实现「财务节点只改税额列」。
五、basic-form.vue:统一 schema 融合
业务审批页通用壳组件basic-form.vue接收fieldPermission、isApproval、processStatus、viewType:
const{isFieldHidden,isFieldReadonly,isFieldEditable,isFieldRequired,hasPermissionConfig,}=useBusinessFieldPermission({permission:fieldPermRef,isApproval:toRef(props,'isApproval'),viewType:toRef(props,'viewType'),processStatus:toRef(props,'processStatus'),});resolvedSchema计算属性遍历formSchema,对有权限配置的字段设置:
hide←isFieldHiddencomponentProps.disabled←isFieldReadonly/isFieldEditablerules←required时强制'required'
consthidden=isFieldHidden(fieldName);constrequired=isFieldRequired(fieldName);letfieldDisabled:boolean;if(hidden){fieldDisabled=globalDisabled;}elseif(isFieldEditable(fieldName)){fieldDisabled=false;}elseif(isFieldReadonly(fieldName)){fieldDisabled=true;}else{fieldDisabled=globalDisabled;}letfieldRules:typeofschema.rules;if(required){fieldRules='required';}elseif(hidden){fieldRules=undefined;}else{fieldRules=schema.rules;}return{...schema,hide:hidden||!!schema.hide,rules:fieldRules,componentProps:{...origCProps,disabled:fieldDisabled},};单一 watcher将resolvedSchema同步到formApi,避免多个 watcher 竞争导致闪烁。
六、与数据权限(行级)的协作
6.1 对比表
| 维度 | 数据权限 | 字段权限 |
|---|---|---|
| 作用层 | MyBatis SQL | Vue 表单 schema |
| 配置位置 | 角色管理 → 数据范围 | 流程设计器 → 节点字段 |
| 典型问题 | 谁能看到哪些客户 | 这张单展示哪些列 |
| 核心类 | DeptDataPermissionRule | BpmFormFieldPermissionDO |
| 失效场景 | 关闭@DataPermission | 未配置则回退旧只读逻辑 |
6.2 组合示例
销售 A 只能看本人客户(SELF 数据权限),打开某张合同审批时:
- 行级:允许访问该
contract_id - 列级:节点「法务」才
WRITE合同条款字段,销售在审批态对金额字段READ
七、流程设计器配置步骤
▲ 流程模型 → 流程设计:以「发起人 → 部门负责人 → …」的节点串联审批链,字段权限正是挂在每一个用户任务节点上——同一张单据,不同节点配置不同的字段可见/可改规则
- 流程模型使用自定义业务表单(路径指向
views/oa/xxx/info) - 业务表需经代码生成登记字段(关联
codegen_table_id) - 在流程设计器选中用户任务节点 →字段权限面板
- 勾选主表/子表字段 → 设为 READ/WRITE/NONE/REQUIRED
- 发布流程后,审批详情 API 按
taskDefinitionKey返回当前节点权限 JSON
▲ 点击审批节点弹出配置抽屉:除「审批人 / 操作按钮设置」外,「表单字段权限」标签页即按字段逐项设置 READ/WRITE/NONE/REQUIRED 的入口;配置落库到bpm_form_field_permission,运行时由useBusinessFieldPermission驱动basic-form.vue动态渲染
7.1 节点权限 vs 表单全局 disabled
| 场景 | 建议 |
|---|---|
| 整单只读查看 | viewType=done或disabled=true |
| 节点差异化 | 配置字段权限,勿仅靠disabled |
| 隐藏敏感列 | 使用 NONE,勿用 disabled 占位 |
八、主表 + 明细表实战模式
8.1 主表:请假头
| 字段 | 发起节点 | 部门经理节点 | HR 节点 |
|---|---|---|---|
| reason | REQUIRED | READ | READ |
| days | REQUIRED | READ | WRITE |
| annualBalance | NONE | NONE | WRITE |
8.2 明细表:报销行
detailFields.expenseItem.amount在财务节点WRITE,其余节点READ。
前端在info/index.vue传入:
<BasicForm:field-permission="approvalDetail?.fieldPermission":is-approval="taskId != null":view-type="viewType":process-status="formData.status":form-schema="formSchema"/>九、后端下发权限(概念链路)
审批详情聚合时,根据processInstanceId+taskId查询:
- 当前 BPMN
activityId bpm_form_field_permissionwheremodel_id+node_id- 组装为
masterFields/detailFieldsDTO
(具体 Service 实现在yudao-module-bpm-server流程实例详情接口中,与 FlowableTask定义 key 绑定。)
十、RuoYi Office 字段权限亮点
10.1 四种权限覆盖 90% 审批 UX
比「仅只读/可编辑」二元模型多了NONE 隐藏与REQUIRED 节点必填,无需写节点监听器。
10.2 与代码生成器字段对齐
fieldName直接对应java_field,设计器可自动列出表字段,减少手工输入错误。
10.3 制单/审批/已办三态清晰
isInEditableState+isApproval+BpmReadonlyViewTypes避免「审批通过后申请人仍能改金额」类漏洞。
10.4 明细表一等支持
detailFields二维 Map + VxeTable 列可见/可编,适配 ERP 风格头行单据。
10.5 无配置平滑降级
hasPermissionConfig === false时走历史computeBusinessFormReadonly逻辑,老表单无需改一行即可上线。
十一、技术亮点总结
| 设计要点 | 实现方式 | 价值 |
|---|---|---|
| 权限枚举 | BpmFieldPermissionEnum1-4 | 前后端契约稳定 |
| 持久化 | bpm_form_field_permission | 按模型+节点版本管理 |
| 主明细 | tableType+detailFields | 复杂单据列级控制 |
| 运行时 | useBusinessFieldPermission | 纯函数式判断,易单测 |
| UI 融合 | basic-formresolvedSchema | 业务页只传 props |
| 与行级正交 | 不改动 DataPermission | 组合解决真实企业需求 |
| 安全兜底 | 非审批态整单只读 | 防止越权改单 |
十二、快速体验
- 在线演示:http://ruoyioffice.com/web/(admin / admin123)
- 路径:流程中心→ 流程模型 → 选择带业务表单的流程 → 节点配置字段权限→ 发起流程并在待办中验证
- 源码重点:
- 后端枚举:
yudao-module-bpm-api/.../BpmFieldPermissionEnum.java - 前端 composable:
apps/web-antd/src/views/bpm/composables/useBusinessFieldPermission.ts - 表单壳:
apps/web-antd/src/components/basic-form/basic-form.vue
- 后端枚举:
| 仓库 | 地址 |
|---|---|
| 后端(GitCode) | https://gitcode.com/zhouzhongyan/ruoyi-office.git |
| 前端(GitCode) | https://gitcode.com/zhouzhongyan/ruoyi-office-vben.git |
| 后端(GitHub) | https://github.com/yuqing2026/ruoyi-office.git |
推荐体验流程:
- 打开已配置字段权限的 OA 流程(如用印、用车)
- 用发起人账号提交,确认制单态字段可填
- 用审批人账号打开待办,对比节点配置的 WRITE/READ/NONE
- 流程结束后用发起人查看已办,确认敏感字段仍不可编辑
- 在流程设计器修改某节点权限,发布后再走一单验证
十三、权限判断决策表(审批态)
便于 AI 抽取与团队对齐,审批态(isApproval=true)下单字段的最终 UI 态:
| getFieldPermission | isFieldHidden | isFieldEditable | isFieldReadonly | isFieldRequired |
|---|---|---|---|---|
| none | true | false | true | false |
| read | false | false | true | false |
| write | false | true | false | false |
| required | false | true | false | true(校验开启) |
| 未配置 | false | false | true | false |
制单态(isInEditableState=true)下,除none外一律可编辑;required在制单态同样触发必填。
十四、与 BPM 流程表单的衔接清单
从 0 到 1 接入字段权限时,按此清单自检:
| 步骤 | 检查项 |
|---|---|
| 1 | 业务表已通过 infra代码生成导入,字段有java_field |
| 2 | 流程模型表单类型为自定义业务表单,路径指向info/index.vue |
| 3 | 详情页使用BasicForm,传入fieldPermission/isApproval/processStatus |
| 4 | formSchema的fieldName与权限配置字段名一致 |
| 5 | 明细表 VxeTable 使用getDetailColumnVisible/isDetailColumnEditable |
| 6 | 流程设计器各用户任务节点已保存字段权限并重新发布 |
| 7 | 服务端 Save 接口对敏感字段做二次校验(防 API 绕过) |
十五、常见踩坑
| 现象 | 原因 | 处理 |
|---|---|---|
| 审批人仍能改只读字段 | 未传isApproval=true | 待办打开时置isApproval |
| 通过后发起人又能改金额 | 非审批态未强制只读 | 确认processStatus传递正确 |
| 字段权限不生效 | masterFields为空 | 检查节点是否配置并发布 |
| 明细列仍能编辑 | 未接isDetailColumnEditable | VxeTable 列配置绑定 composable |
| REQUIRED 不弹校验 | rules被业务 schema 覆盖 | 看resolvedSchema是否设为required |
结语
字段权限解决的是「同一行数据,不同参与者看到不同列」——这是审批系统的刚需,不能靠数据权限或功能权限替代。RuoYi Office 用枚举 + 流程设计器配置 + composable 运行时把复杂度关在 infra/bpm 层,业务表单只需BasicForm传fieldPermission。
可与 数据权限设计文章、代码生成器实战 组成「权限三部曲」。你们审批表单怎么做字段控制的?欢迎评论区交流。
常见问题(FAQ)
字段权限和数据权限有什么区别?
数据权限控制列表/查询 SQL 能返回哪些行(如仅本部门);字段权限控制打开一张单据后哪些列显示、可编辑或隐藏。两者同时使用不冲突。
没有配置字段权限的老流程会怎样?
useBusinessFieldPermission检测masterFields为空时hasPermissionConfig=false,回退到基于viewType、isApproval、processStatus的原有只读逻辑,无需升级即可运行。
NONE 和把字段设为 disabled 有什么不同?
NONE 会hide字段,DOM 不渲染,用户无法在审查元素里看到值;disabled 仍可见,不适合薪资等敏感信息。
明细表字段如何命名?
detailFields的第一层 key 为明细表标识(与代码生成 business 名或页面约定一致),第二层为列fieldName,与 VxeTablefield一致。
能否只做后端校验不做前端隐藏?
不建议。前端隐藏提升体验并减泄露;后端 SaveReqVO 仍应对敏感字段做服务端校验,防止绕过 API 提交。
💡想要体验 RuoYi Office 的强大功能?
🌐在线演示:http://ruoyioffice.com/web/(账号 admin / admin123)
📦源码仓库:GitCode | GitHub
💬技术咨询:添加微信17156169080,备注「RuoYi Office」
⭐如果觉得不错,请给个 Star 支持一下!
