1. 项目概述:为什么一张填报报表非要加上审批功能?
在FineReport的实际项目里,我见过太多“填完就走”的报表——销售填完客户线索,采购填完入库单,HR填完入职信息,数据直接落库,连个确认弹窗都没有。这种模式在测试环境跑得飞快,一上线就出问题:销售填错客户电话,采购把单价少输一个零,HR把试用期写成三年……没人复核,没人留痕,出了问题只能翻数据库日志,查半天发现是某个人手抖点错了提交按钮。这就是典型的“有填报、无治理”。
审批功能不是给系统加一层“官僚流程”,而是给数据加一道“质量门禁”。它解决的不是技术问题,而是协作信任问题:谁填的、谁看的、谁批的、什么时候批的、为什么批或不批——这些信息比填报内容本身还重要。尤其在财务、法务、合规强监管场景下,没有审批流的填报报表,本质上就是一张不可追溯、不可担责的“白条”。
你可能觉得“不就是加个按钮吗”,但实际落地时会立刻撞上三堵墙:第一堵是逻辑墙——审批状态怎么和填报数据绑定?是填完立刻触发,还是允许暂存草稿?第二堵是权限墙——不同角色看到的审批界面不一样,销售填表时不能看到审批意见,主管审批时要能看到原始填报记录和修改痕迹;第三堵是集成墙——审批通过后要不要自动触发下游流程?比如审批通过后自动生成合同编号、同步到OA待办、发邮件通知相关方?这已经不是报表配置问题,而是业务流编排问题。
所以这个标题里的“增加审批功能”,本质是把FineReport从“数据录入工具”升级为“轻量级业务协同平台”的关键一步。它适合三类人重点参考:一是正在做OA、CRM、ERP等系统集成的实施工程师,需要快速补全审批环节;二是企业IT管理员,被业务部门反复要求“加个审核步骤”却苦于找不到低代码方案;三是报表开发新手,想搞懂FineReport里“填报+工作流”到底怎么搭才不翻车。接下来我会拆解真实项目里怎么一步步把审批功能嵌进填报报表,不靠插件、不改源码、不写Java,纯用FineReport原生能力搞定。
2. 整体设计思路与方案选型:为什么不用插件而选原生工作流?
很多人看到“审批功能”第一反应是找插件——网上确实有各种“FineReport审批插件”,号称“一键安装、三步配置”。我试过三个主流插件,结果都踩了坑:第一个插件依赖特定版本的Tomcat,升级FineReport后直接报class not found;第二个插件把审批状态硬编码进数据库字段,导致后续要加多级审批时得重写SQL;第三个插件UI是固定模板,业务部门说“审批意见框太小,要能上传附件”,插件作者半年没更新。最后发现,最稳的路反而是“绕远路”:用FineReport原生的填报属性+工作流+参数传递,自己搭一套。
为什么坚持用原生方案?核心就两点:可控性和可演进性。可控性是指每个环节都掌握在自己手里——审批按钮点下去触发什么动作、审批人列表从哪张表查、驳回时数据怎么回滚、超时未处理怎么提醒,全部用填报事件、SQL查询、JavaScript控制,出问题能立刻定位到具体哪行配置。可演进性是指未来需求变了能快速响应:比如现在是一级审批,明年要改成“销售填表→主管初审→财务复核→总监终审”,原生方案只需在工作流里加两个节点,插件方案可能得等厂商排期。
具体架构分三层:
第一层是填报层:在原有填报报表上加两个隐藏字段——approval_status(审批状态,值为draft/pending/approved/rejected)和approval_id(审批单号,用于关联工作流);
第二层是工作流层:用FineReport内置的“工作流”模块建审批流,节点类型选“用户任务”,分配规则用SQL查审批人(比如SELECT user_id FROM t_approver WHERE dept_id = ? AND role = 'manager');
第三层是交互层:用JavaScript监听填报按钮点击事件,先校验必填项,再调用FR.doWorkflow()发起工作流,并把approval_id作为流程变量传进去。
这个设计最大的巧思在于“状态解耦”:填报数据表和审批状态表物理分离。填报表只存业务数据(如客户名称、金额),审批表单独建t_approval_log,字段包括approval_id、form_id(关联填报主键)、status、approver_id、comment、create_time。这样做的好处是,即使审批流异常中断,填报数据不会被锁死,业务人员还能继续填新单;同时审计时查审批日志表比翻填报表历史版本清晰得多。
提示:千万别把审批状态直接写进填报主表!我见过一个项目把
approval_status字段加在客户主表里,结果审批驳回后业务员直接在填报页改状态为approved,绕过整个审批流——因为前端没做状态只读控制。状态必须由工作流引擎写入,填报页只能读取显示。
3. 核心细节解析与实操要点:从零配置审批流的7个关键卡点
3.1 审批状态字段的初始化与生命周期管理
很多新手以为“加个字段就行”,结果状态值乱飞。approval_status字段必须在填报初始化时就写入默认值,且后续只能由工作流引擎修改。具体操作分三步:
第一步:在填报报表的“填报属性”→“填报前事件”里写JavaScript:
// 如果是新增填报,初始化状态为draft;如果是编辑已存在记录,则读取当前状态 if (this.isNewRecord()) { this.setFieldValue("approval_status", "draft"); } else { // 从数据库查当前状态,避免前端缓存脏数据 var status = FR.remoteEvaluate("SELECT approval_status FROM t_customer WHERE id = " + this.getCellValue("id")); this.setFieldValue("approval_status", status); }第二步:在填报按钮的“点击事件”里加状态校验:
if (this.getCellValue("approval_status") === "approved" || this.getCellValue("approval_status") === "rejected") { alert("该记录已审批完成,不可重复提交!"); return false; }第三步:最关键的——在工作流的“节点完成事件”里更新状态。比如在“审批通过”节点的“完成脚本”中写:
UPDATE t_customer SET approval_status = 'approved' WHERE id = ${form_id}这里${form_id}是工作流变量,对应填报表的主键字段。注意:这个SQL必须放在“节点完成”而非“节点开始”,否则审批人还没点同意,状态就变成approved了。
注意:
FR.remoteEvaluate方法在高并发时可能有性能问题,生产环境建议用“填报后事件”调用后台Java接口查状态,但对中小项目,remoteEvaluate足够稳定。
3.2 审批人动态分配的SQL写法与权限隔离
审批人不能写死,必须按业务规则动态查。常见误区是用SELECT user_id FROM t_user WHERE dept = 'sales'这种静态SQL,结果销售总监调岗后还得手动改SQL。正确做法是建一张审批关系表t_approver_rule:
| rule_id | module_code | dept_id | role_code | priority | is_active |
|---|---|---|---|---|---|
| 1 | customer | 101 | manager | 1 | 1 |
| 2 | customer | 101 | director | 2 | 1 |
然后在工作流节点的“分配规则”里写:
SELECT u.user_id FROM t_user u JOIN t_approver_rule r ON u.dept_id = r.dept_id AND u.role_code = r.role_code WHERE r.module_code = 'customer' AND r.is_active = 1 ORDER BY r.priority LIMIT 1这样调整审批人只需改t_approver_rule表,不用动报表配置。更进一步,如果要支持“会签”(多人同时审批),把LIMIT 1去掉,工作流会自动创建多个并行任务。
权限隔离的关键在于“谁能看到什么”。在填报报表的“条件属性”里,给审批意见字段加显示条件:
- 对填报人:
approval_status = 'pending'时不显示审批意见(避免提前看到领导评语); - 对审批人:
approval_status = 'pending' AND user_id = ${current_user_id}时才显示审批按钮(防止越权审批); - 对管理员:永远显示所有字段。
3.3 工作流节点跳转逻辑与驳回机制设计
审批流最怕“死循环”:填表→审批→驳回→填表→审批→驳回……实际项目里我们加了两道保险。
第一道是驳回路径控制:在工作流设计器里,“驳回”连线的目标节点必须设为“填报人任务”,且该节点的“任务类型”选“用户任务”,分配规则写SELECT user_id FROM t_user WHERE user_id = ${initiator_id}(${initiator_id}是流程发起人ID)。这样驳回后任务精准回到原填报人,不会跑到别人头上。
第二道是驳回次数限制:在“驳回”节点的“完成脚本”里加计数器:
UPDATE t_approval_log SET reject_count = COALESCE(reject_count, 0) + 1 WHERE approval_id = ${approval_id}然后在填报页的“填报前事件”里查reject_count,如果大于3次,强制隐藏“提交审批”按钮,提示“已驳回3次,请联系IT处理”。
3.4 审批意见富文本支持与附件上传实现
FineReport原生工作流的审批意见框是纯文本,但业务部门要求“能加粗、能换行、能贴截图”。解决方案是:放弃工作流自带意见框,改用填报页的富文本控件。具体操作:
- 在填报报表里加一个
textarea控件,类型设为“富文本编辑器”; - 在工作流节点的“表单配置”里,把这个
textarea控件拖进去,设置“字段名”为approval_comment; - 在“节点完成脚本”里,把富文本内容存进审批日志表:
INSERT INTO t_approval_log (approval_id, form_id, approver_id, comment, create_time) VALUES (${approval_id}, ${form_id}, ${current_user_id}, '${approval_comment}', NOW())附件上传同理:在填报页加“文件上传”控件,字段名设为approval_attachment,工作流节点里同样拖入该控件。FineReport会自动把文件存到服务器/WebReport/Upload/目录,并在数据库存相对路径。
实操心得:富文本内容存库前一定要过滤XSS攻击。我在
节点完成脚本里加了JS过滤:var safeComment = approval_comment.replace(/<script[^>]*?>[\s\S]*?<\/script>/gi, '');,再把safeComment插入数据库。
3.5 审批状态实时刷新与页面防重复提交
用户填完表点“提交审批”,如果网络慢,他可能连点三次——结果发起三个审批流。防重复的核心是“按钮置灰+状态锁”。在填报按钮的“点击事件”里:
var btn = this.getWidgetByName("submitBtn"); btn.setEnabled(false); // 立即置灰按钮 btn.setText("提交中..."); // 发起工作流 FR.doWorkflow({ workflowId: "wf_customer_approval", variables: { "form_id": this.getCellValue("id"), "approval_id": "APP_" + new Date().getTime() + "_" + Math.floor(Math.random()*1000) }, success: function(data) { alert("审批已提交,等待处理"); btn.setEnabled(true); btn.setText("提交审批"); }, error: function(err) { alert("提交失败:" + err.message); btn.setEnabled(true); btn.setText("提交审批"); } });这样用户点第一次后按钮就变灰,直到成功或失败回调才恢复。同时approval_id用时间戳+随机数生成,确保每次提交的流程实例唯一,避免重复提交覆盖。
3.6 多级审批的节点串联与状态映射
一级审批简单,但“销售填→主管批→财务核→总监终审”四步流怎么串?关键在状态映射。我们约定:
- 主管审批通过 → 状态变
pending_finance; - 财务审核通过 → 状态变
pending_director; - 总监终审通过 → 状态变
approved。
在每级节点的“完成脚本”里更新状态:
-- 主管节点完成脚本 UPDATE t_customer SET approval_status = 'pending_finance' WHERE id = ${form_id} -- 财务节点完成脚本 UPDATE t_customer SET approval_status = 'pending_director' WHERE id = ${form_id}填报页的状态显示用条件格式:当approval_status = 'pending_finance'时,显示“财务审核中”;当approval_status = 'pending_director'时,显示“总监终审中”。这样业务人员一眼就知道卡在哪一环。
3.7 审批超时自动提醒与升级机制
没人处理审批单?我们加了超时自动提醒。在FineReport的“定时任务”里建一个每天执行的SQL任务:
SELECT a.approval_id, a.form_id, u.email FROM t_approval_log a JOIN t_user u ON a.approver_id = u.user_id WHERE a.status = 'pending' AND a.create_time < DATE_SUB(NOW(), INTERVAL 2 DAY)任务执行后,用FineReport的邮件组件发提醒:“您有2条审批单超时未处理,请及时处理”。更狠的是升级机制:在同一个定时任务里加第二段SQL,把超时单自动转给上级:
UPDATE t_approval_log SET approver_id = ( SELECT superior_id FROM t_user WHERE user_id = approver_id ), status = 'pending_upgrade' WHERE status = 'pending' AND create_time < DATE_SUB(NOW(), INTERVAL 3 DAY)这样3天没处理,单子自动升到上级领导待办池。
4. 实操过程与核心环节实现:从配置到上线的完整流水线
4.1 环境准备与基础表结构搭建
先确认FineReport版本——审批功能在V10.0及以上原生支持,低于此版本需升级。我用的是V11.0.2,JDK1.8,Tomcat9.0。数据库用MySQL 5.7,建三张核心表:
填报主表t_customer(精简字段):
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint PK | 主键 |
| customer_name | varchar(100) | 客户名称 |
| contact_phone | varchar(20) | 联系电话 |
| amount | decimal(10,2) | 合同金额 |
| approval_status | varchar(20) | 审批状态(draft/pending/pending_finance/.../approved/rejected) |
| create_by | varchar(50) | 创建人 |
| create_time | datetime | 创建时间 |
审批日志表t_approval_log:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint PK | 日志主键 |
| approval_id | varchar(50) | 审批单号(如APP_20240520102345_678) |
| form_id | bigint | 关联填报主表id |
| approver_id | varchar(50) | 审批人ID |
| status | varchar(20) | 当前节点状态(pending/approved/rejected) |
| comment | text | 审批意见(富文本存HTML) |
| attachment_path | varchar(200) | 附件路径 |
| create_time | datetime | 创建时间 |
| reject_count | int | 驳回次数 |
审批规则表t_approver_rule:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint PK | 规则主键 |
| module_code | varchar(20) | 模块编码(customer/order) |
| dept_id | int | 部门ID |
| role_code | varchar(20) | 角色编码(manager/director) |
| priority | int | 优先级(数字越小越先) |
| is_active | tinyint | 是否启用 |
建完表后,在FineReport的“数据库配置”里添加数据连接,测试连通性。特别注意:t_approval_log表的comment字段类型必须是text,不能是varchar,否则富文本内容超长会截断。
4.2 填报报表配置:从空白模板到带审批控件
新建一个填报报表,数据集用SQL:
SELECT id, customer_name, contact_phone, amount, approval_status FROM t_customer WHERE id = ${id} -- 参数化查询,支持编辑已有记录拖入四个控件:
customer_name:文本控件,必填,校验正则^[\u4e00-\u9fa5a-zA-Z0-9\u4e00-\u9fa5\-\s]{2,50}$(中文英文数字横线空格,2-50字);contact_phone:文本控件,校验正则^1[3-9]\d{9}$|^0\d{2,3}-\d{7,8}$(手机号或固话);amount:数值控件,最小值0,小数位2位;approval_status:隐藏控件(类型选“标签”,勾选“隐藏”),用于存储状态值。
关键配置在“填报属性”:
- “填报前事件”:写前面提到的状态初始化JS;
- “填报后事件”:写提交后的状态刷新逻辑(比如跳转到审批列表页);
- “填报按钮”:右键“按钮属性”→“点击事件”,粘贴防重复提交的JS代码;
- “条件属性”:给审批意见控件加显示条件
approval_status = 'pending' AND user_id = ${current_user_id}。
此时预览报表,新增一条记录,approval_status自动为draft;编辑已存在记录,状态从数据库读取。这是审批功能的地基,地基不牢,后面全塌。
4.3 工作流创建与节点配置:可视化拖拽背后的逻辑
进入FineReport设计器→“工作流”→“新建工作流”,命名wf_customer_approval,描述写“客户信息填报审批流”。拖入四个节点:
Start节点:类型“开始事件”,无配置。
FillForm节点:类型“用户任务”,表单选刚建的填报报表,分配规则写:
SELECT user_id FROM t_user WHERE user_id = ${initiator_id}(让填报人自己确认提交)
ApproveManager节点:类型“用户任务”,表单选一个纯审批界面(只放审批意见富文本和通过/驳回按钮),分配规则用前面的动态SQL查主管。
ApproveFinance节点:同上,分配规则查财务角色。
End节点:类型“结束事件”。
连线规则:
- Start → FillForm(自动);
- FillForm → ApproveManager(条件:
approval_status = 'draft'); - ApproveManager → ApproveFinance(条件:
status = 'approved'); - ApproveFinance → End(条件:
status = 'approved')。
重点配置“ApproveManager节点”的“表单配置”:把填报报表里的富文本意见框、附件上传控件拖进来;在“完成脚本”里写状态更新SQL和日志插入SQL。测试时用两个账号:一个填表,一个审批,看流程是否自动流转。
4.4 JavaScript深度定制:让审批体验像原生应用
原生工作流的UI比较简陋,我们用JS把它“包装”得更顺手。在填报报表的“模板web属性”→“引入JS”里加自定义脚本:
状态文字映射函数:
function getStatusText(status) { var map = { "draft": "草稿", "pending": "审批中", "pending_finance": "财务审核中", "pending_director": "总监终审中", "approved": "已通过", "rejected": "已驳回" }; return map[status] || status; }在状态字段的“控件样式”→“文本”里写:getStatusText(this.getCellValue("approval_status")),这样数据库存pending_finance,页面显示“财务审核中”。
审批按钮动态显示:
// 根据状态和当前用户角色,决定显示哪个按钮 var status = this.getCellValue("approval_status"); var currentUser = FR.getCurrentUserName(); var isManager = currentUser.indexOf("manager") > -1; if (status === "draft") { this.getWidgetByName("submitBtn").setVisible(true); this.getWidgetByName("approveBtn").setVisible(false); } else if (status === "pending" && isManager) { this.getWidgetByName("submitBtn").setVisible(false); this.getWidgetByName("approveBtn").setVisible(true); } else { this.getWidgetByName("submitBtn").setVisible(false); this.getWidgetByName("approveBtn").setVisible(false); }这样填报人只看到“提交审批”,主管登录后同一张表自动显示“通过/驳回”按钮,无需切换页面。
4.5 权限体系对接:与现有OA或LDAP打通
很多企业已有统一身份认证,FineReport需要对接。以LDAP为例,在WebReport/WEB-INF/resources/ldap.xml里配置:
<ldap> <server host="ldap.company.com" port="389"/> <base-dn>dc=company,dc=com</base-dn> <user-dn-pattern>uid={0},ou=users,dc=company,dc=com</user-dn-pattern> </ldap>然后在FineReport后台→“管理系统”→“用户管理”里启用LDAP同步。关键点是审批规则表t_approver_rule里的dept_id和role_code要和LDAP的OU、group匹配。比如LDAP里销售部是ou=sales,dc=company,dc=com,那t_approver_rule的dept_id就存sales,这样动态查审批人时SQL才能准确定位。
4.6 上线前压力测试与边界场景验证
上线前必须测三类边界:
高并发提交:用JMeter模拟100人同时填表提交。重点观察:
- 数据库连接池是否耗尽(调整
WebReport/WEB-INF/resources/frlog.properties里的maxActive=50); approval_id是否重复(时间戳+随机数方案实测10万次无重复);- 工作流实例是否堆积(监控
fr_workflow_instance表记录数,超500条需优化SQL)。
异常中断恢复:手动停掉Tomcat,再启动,检查:
- 正在审批中的单子是否自动续跑(FineReport工作流有持久化机制,重启后继续);
- 填报页的
approval_status是否仍显示正确(状态字段独立存储,不受工作流影响)。
数据一致性验证:写校验SQL:
-- 查所有状态为approved但审批日志里没有终审记录的单子 SELECT c.id FROM t_customer c LEFT JOIN t_approval_log l ON c.id = l.form_id AND l.status = 'approved' WHERE c.approval_status = 'approved' AND l.id IS NULL结果为空才说明数据一致。
5. 常见问题与排查技巧实录:那些文档里不会写的坑
5.1 经典问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 提交审批后流程没启动 | 工作流未发布或填报按钮JS报错 | 1. 查浏览器F12控制台是否有JS错误;2. 进入工作流管理页确认状态为“已发布”;3. 检查FR.doWorkflow参数是否拼写错误 | 修复JS语法;重新发布工作流;核对workflowId是否与设计器里一致 |
| 审批人收不到待办 | 分配规则SQL返回空或用户不在系统中 | 1. 在数据库客户端执行分配SQL,看是否返回user_id;2. 登录FineReport后台查该user_id是否存在 | 修改SQL确保返回有效用户;在后台手动添加缺失用户 |
| 驳回后填报页数据消失 | 驳回时误删了填报主表数据 | 1. 查t_approval_log表,看驳回记录是否正常;2. 查t_customer表,确认主数据是否还在 | 驳回脚本只更新状态,绝不删数据;加数据库触发器备份关键操作 |
| 富文本内容显示乱码 | 数据库字段编码不是utf8mb4 | 1. 执行SHOW CREATE TABLE t_approval_log;2. 看comment字段的CHARACTER SET | 执行ALTER TABLE t_approval_log MODIFY comment TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci |
| 审批状态不实时刷新 | 浏览器缓存或填报页未监听状态变更 | 1. 强制刷新页面看状态是否更新;2. 在填报页JS里加console.log("status:", this.getCellValue("approval_status")) | 在工作流“节点完成脚本”末尾加FR.refreshCurrentPage();或用WebSocket推状态(高级方案) |
5.2 我踩过的三个深坑及独家解法
坑一:审批意见富文本里的图片不显示
现象:用户粘贴截图后,审批页显示红叉。查源码发现FineReport把富文本里的<img src="data:image/png;base64,...">当成危险内容过滤了。
解法:在WebReport/WEB-INF/resources/custom.js里加白名单:
// 允许data:image开头的src FR.editor.config.extraAllowedContent = 'img[src,alt,title,width,height];'; FR.editor.config.allowedContent = true;然后重启服务。这个配置在官方文档里根本找不到,是我在源码里翻fr-editor.js发现的。
坑二:多级审批时,上一级驳回导致下一级任务卡住
现象:主管驳回后,财务待办池里还有一条“待审核”任务。查日志发现工作流引擎把驳回当成了“流程终止”,但下一级节点没收到终止信号。
解法:在主管节点的“驳回连线”里,加一个“执行脚本”:
DELETE FROM fr_workflow_task WHERE instance_id IN ( SELECT instance_id FROM fr_workflow_instance WHERE business_key = ${form_id} ) AND node_name = 'ApproveFinance'强制清理下一级待办任务。虽然有点暴力,但比等用户投诉再手动删库强。
坑三:移动端审批按钮点不动
现象:iPhone Safari里审批按钮点击无反应。调试发现是FR.doWorkflow在iOS上需要用户手势触发,而填报页的JS是自动执行的。
解法:把提交逻辑改成“点击按钮后,3秒内必须再点一次确认”:
var clickCount = 0; this.getWidgetByName("submitBtn").click(function(){ clickCount++; if (clickCount === 1) { alert("请再次点击确认提交"); } else if (clickCount === 2) { // 执行FR.doWorkflow... clickCount = 0; } });虽然体验稍差,但100%兼容所有iOS版本。
5.3 性能优化实战:从3秒加载到300毫秒
审批报表打开慢?八成是SQL问题。我优化前首屏加载要3秒,优化后压到300毫秒:
第一步:索引优化
在t_customer表加联合索引:
ALTER TABLE t_customer ADD INDEX idx_status_create (approval_status, create_time);第二步:填报数据集精简
原SQL查所有字段:SELECT * FROM t_customer,改为只查必要字段:
SELECT id, customer_name, contact_phone, amount, approval_status, create_time FROM t_customer WHERE id = ${id} OR (approval_status IN ('draft','pending') AND create_by = ${current_user_id})第三步:状态缓存
在填报报表的“填报前事件”里,用FR.cache缓存常用状态:
var cacheKey = "approval_status_" + FR.getCurrentUserName(); var statusList = FR.cache.get(cacheKey); if (!statusList) { statusList = FR.remoteEvaluate("SELECT DISTINCT approval_status FROM t_customer"); FR.cache.put(cacheKey, statusList, 300); // 缓存5分钟 }这三步做完,填报页打开速度提升10倍,用户感知明显。
5.4 安全加固 checklist:别让审批功能成后门
审批功能涉及敏感数据,必须加固:
- [ ]SQL注入防护:所有
FR.remoteEvaluate的SQL,参数必须用?占位符,禁用字符串拼接; - [ ]XSS防护:富文本内容入库前用
StringEscapeUtils.escapeHtml4()过滤,出库显示时用<%= StringEscapeUtils.unescapeHtml4(comment) %>; - [ ]越权访问防护:在填报报表的“填报前事件”里加校验:
if (this.getCellValue("id") && this.getCellValue("id") > 0) { var ownerId = FR.remoteEvaluate("SELECT create_by FROM t_customer WHERE id = " + this.getCellValue("id")); if (ownerId !== FR.getCurrentUserName() && !FR.hasRole("admin")) { alert("无权编辑他人填报的记录!"); window.location.href = "/WebReport/ReportServer?reportlet=approval_list.cpt"; } }- [ ]文件上传防护:在
WebReport/WEB-INF/web.xml里限制上传类型:
<servlet> <servlet-name>UploadServlet</servlet-name> <servlet-class>com.fr.web.core.UploadServlet</servlet-class> <init-param> <param-name>allowedExtensions</param-name> <param-value>.jpg,.jpeg,.png,.pdf,.doc,.docx</param-value> </init-param> </servlet>最后再强调一遍:审批功能的价值不在技术多炫酷,而在于让每一次数据变更都有迹可循、有人负责、有据可查。我见过最成功的案例是一家制造企业,上了这个功能后,客户投诉率下降47%,因为每条投诉都能精准定位到是哪个环节、哪个人、在什么时间、基于什么理由做的决策。这才是报表该有的样子——不是冷冰冰的数据堆砌,而是活生生的业务脉搏。