1. 为什么我们需要浏览器直开与一键打印功能
在日常办公中,财务和人事部门经常需要处理大量的表单附件。以财务报销流程为例,员工上传的发票、合同等附件往往需要打印存档。传统的处理方式是:先下载附件到本地,再用专业软件打开打印,最后还要记得删除下载的临时文件。这个过程不仅繁琐,还容易出错。
我见过不少同事因为忘记删除下载文件,导致电脑存储空间不足。更糟的是,有些敏感文件可能因此泄露。PDF文件尤其麻烦,因为大多数办公电脑默认用Adobe Reader打开,这个软件启动速度慢,打印设置又复杂。
Ecology9系统虽然提供了在线预览图片的功能,但对PDF等文件却强制要求下载。这就造成了效率瓶颈。想象一下,财务人员每天要处理上百份报销单,每份都要重复"下载-打印-删除"的步骤,时间都浪费在这些机械操作上了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术实现原理剖析
2.1 前端组件重写机制
Ecology9系统使用的是基于React的前端框架,其上传组件WeaUpload可以通过ecodeSDK进行功能扩展。这就像给汽车加装定速巡航功能 - 我们不需要重新造车,只需在现有系统上添加新功能。
核心思路是拦截组件的props属性,在文件列表的每个项目后面插入自定义按钮。这里用到了装饰器模式,就像给手机套上保护壳的同时,还在壳上加了个指环扣。具体来说,我们通过ecodeSDK.overwritePropsFnQueueMapSet方法,在组件渲染前注入自定义逻辑。
2.2 文件链接处理技巧
系统生成的下载链接通常包含&download=1参数,这会导致浏览器直接下载文件。我们的代码使用正则表达式移除这个参数,就像把"请立即下载"的指令改成"请直接展示"。这样修改后,PDF文件就会在浏览器内置的预览器中打开。
浏览器处理PDF的方式很有意思:Chrome和Edge都有自己的PDF渲染引擎,就像手机自带照片查看器。这种方式比用Adobe Reader快得多,而且打印界面也更简洁。
3. 完整实现步骤详解
3.1 环境准备与代码部署
首先需要在Ecology9的开发环境中创建前端脚本文件。建议使用独立的JS文件,而不是直接修改系统源码。这就好比在墙上挂画时使用无痕钉,不会破坏原有结构。
部署时要注意加载顺序,必须确保脚本在WeaUpload组件加载前执行。这就像要先准备好调料再下锅炒菜。在Ecode平台发布时,记得勾选"前置加载"选项。
javascript复制// 文件:custom-upload-extension.js
ecodeSDK.overwritePropsFnQueueMapSet('WeaUpload', {
fn: (newProps) => {
// 检查当前页面是否为流程表单页面
if (!ecodeSDK.checkLPath('/spa/workflow/static4form/index.html#/main/workflow/req'))
return newProps;
// 排除图片类型和未设置uploadId的情况
if (!newProps.uploadId || newProps.listType == 'img')
return newProps;
// 确保表单SDK已加载
if (!WfForm) return newProps;
// 获取当前流程ID并检查是否在允许列表中
let workflowid = "-1";
if (WfForm.getBaseInfo()) {
workflowid = WfForm.getBaseInfo().workflowid;
}
const allowedWorkflows = [176, 170, 221, 165];
if (!allowedWorkflows.includes(workflowid))
return newProps;
// 添加自定义class用于DOM查询
newProps.className = `${newProps.className} wea-upload-add-more-button wea-upload-add-more-button-${newProps.uploadId}`;
// 后续DOM操作代码...
return newProps;
}
});
3.2 DOM操作与按钮添加
这部分代码就像是在每个文件条目后面"缝"上一个新按钮。使用MutationObserver可能会更优雅,但考虑到兼容性,这里采用了轮询检查的方式。
javascript复制// 在newProps处理函数中继续添加:
const querycls = `.wea-upload-add-more-button-${newProps.uploadId} .wea-upload-list-item`;
let dom = document.querySelectorAll(querycls);
const addViewButtons = (items) => {
if (items.length === 0) {
setTimeout(() => {
addViewButtons(document.querySelectorAll(querycls));
}, 1000);
return;
}
items.forEach((item, index) => {
if (item.className.includes('gu-mirror')) return;
const fileData = newProps.datas[index];
if (!fileData) return;
const buttonId = `wea-upload-openbutton${fileData.fileid}`;
if (item.querySelector(`#${buttonId}`)) return;
const icon = document.createElement("i");
icon.id = buttonId;
icon.className = "anticon anticon-search";
icon.style = "float: right; font-size: 15px; margin: 7px 6px 0px 3px;cursor:pointer;";
icon.title = "打开附件";
const filelink = fileData.loadlink.replace(/&download=1/g, '');
icon.onclick = () => openFullWindowHaveBar(filelink);
item.querySelector("i").after(icon);
});
};
addViewButtons(dom);
4. 实际应用中的注意事项
4.1 流程白名单管理
代码中设置了允许使用该功能的流程ID白名单。这就像给特殊通道设置门禁卡,只有授权的流程才能使用这个便捷功能。维护这个列表时要注意:
- 不要一次性开放给所有流程,先从高频场景开始
- 定期审查列表,移除不再需要的流程
- 可以考虑将列表配置化,通过后台管理界面维护
4.2 浏览器兼容性处理
虽然现代浏览器都支持PDF预览,但细节上有差异:
- Chrome:打印对话框最简洁,默认设置最合理
- Edge:功能类似Chrome,但界面略有不同
- Firefox:需要检查pdf.js的版本
- Safari:Mac用户需要注意打印边距设置
建议在代码中加入浏览器检测逻辑,针对不同浏览器给出提示。比如在Safari中提醒用户检查打印边距。
4.3 移动端适配考虑
在手机和平板上,这个功能需要特别处理:
- 按钮大小要调大,方便触控操作
- 考虑禁用打印功能或给出友好提示
- 文件预览可能遇到屏幕尺寸问题
可以通过检查navigator.userAgent来区分设备类型,动态调整功能。
5. 功能扩展与优化方向
5.1 批量打印功能增强
当前实现是单文件打印,可以扩展为批量操作:
javascript复制// 在工具栏添加"批量打印"按钮
function addBatchPrintButton() {
const toolbar = document.querySelector('.workflow-toolbar');
if (!toolbar || toolbar.querySelector('#batch-print-btn')) return;
const btn = document.createElement('button');
btn.id = 'batch-print-btn';
btn.className = 'ant-btn ant-btn-primary';
btn.innerHTML = '<i class="anticon anticon-printer"></i> 批量打印';
btn.onclick = () => {
const links = [];
document.querySelectorAll('[id^="wea-upload-openbutton"]').forEach(btn => {
links.push(btn.onclick.toString().match(/openFullWindowHaveBar\('(.*?)'\)/)[1]);
});
links.forEach(link => {
const win = window.open(link, '_blank');
setTimeout(() => {
win.print();
}, 1000);
});
};
toolbar.appendChild(btn);
}
// 在DOM加载完成后执行
setTimeout(addBatchPrintButton, 3000);
5.2 打印设置记忆功能
可以结合localStorage保存用户的打印偏好:
javascript复制function getPrintSettings(fileType) {
const defaults = {
pdf: {
layout: 'portrait',
margins: 'default',
background: false
},
image: {
size: 'fit',
orientation: 'auto'
}
};
const saved = JSON.parse(localStorage.getItem('printSettings') || '{}');
return {...defaults[fileType], ...saved[fileType]};
}
// 在打印前应用设置
function applyPrintSettings(win, fileType) {
const settings = getPrintSettings(fileType);
// 通过postMessage与打印页面通信
win.postMessage({
type: 'PRINT_SETTINGS',
payload: settings
}, '*');
}
6. 性能优化与调试技巧
6.1 延迟加载优化
对于包含大量附件的表单,可以优化按钮添加逻辑:
javascript复制// 使用IntersectionObserver实现懒加载
function lazyAddButtons() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const item = entry.target;
const index = [...item.parentNode.children].indexOf(item);
addButtonToItem(item, newProps.datas[index]);
observer.unobserve(item);
}
});
}, {rootMargin: '50px'});
document.querySelectorAll(querycls).forEach(item => {
observer.observe(item);
});
}
6.2 错误处理与日志记录
完善的错误处理能提升用户体验:
javascript复制function safeOpenFile(link) {
try {
const win = openFullWindowHaveBar(link);
win.addEventListener('error', (e) => {
console.error('文件打开失败:', e.message);
alert('文件打开失败,请尝试下载后查看');
});
return win;
} catch (err) {
console.error('打开文件异常:', err);
alert('系统繁忙,请稍后再试');
}
}
在实际项目中,我遇到过因为文件服务器临时不可用导致功能中断的情况。后来我们增加了自动重试机制和友好的错误提示,用户体验大幅提升。
