如何在单据分录平铺展示附件
1 业务背景
目前标准产品仅支持了两种附件控件用于单据的附件存放,开发平台单据模板里一般是预置了一个附件面板的,对于单据体分录而言,需要使用附件字段来存放附件,而附件字段实际上是一个多选基础资料,打开附件字段会打开了一个独立的上传附件的动态表单页面,支持在一行分录上传多个附件。如果客户只希望一行分录只记录一个附件,使用附件字段会出现每次查看附件需要打开附件上传表单,然后再次点击附件进行预览或下载操作。这种重复操作或多或少会影响用户的使用体验。那么有没有方法可以让附件在分录平铺显示快速查看附件呢?
2 解决方案
这里可以考虑使用文本字段来代理附件操作,可以使用附件面板来存放附件,单独在单据体配置文本字段编辑风格选择按钮点击编辑并开启启用超链接按钮,将上传逻辑放在按钮触发,预览或下载使用超链接触发即可,但附件面板与分录映射关系不好界定,同时附件面板后台操作二开复杂度较大,开发成本高,这里建议使用附件字段与文本字段组合的方式处理。下面就介绍一下该方案的二开流程。
首先,在需要二开的单据分录里新增一个附件字段与文本字段,记录下单据体以及这两个控件的标识以备代码使用。如下图,两个字段均命名为分录附件,方便关联搜索。
附件字段需要单独配置表名,用于记录单据体与附件字段的关联,另外根据业务需要设置文件上传的最大数量(该场景建议配置为1,多文件会涉及排序以及前端文本显示控制的问题),配置后开启隐藏元素,只显示文本字段即可。然后,修改文本字段的四个属性,如下图,最大长度需要根据文件名的最大长度设置,建议设置大点。
下面开始设计表单插件,首先单据体按钮相关操作的监听事件,按钮点击以及超链接点击。
private final Log log = LogFactory.getLog(AttachFieldEntryEditPlugin.class); private final static String ATTACH_ENTRY = "entryentity"; // 附件字段所在分录标识 private final static String ATTACH_FIELD = "inja_attachmentfield1"; // 附件字段标识 - 不可见 private final static String ATTACH_FIELD_TEXT = "inja_textfield1"; // 文本字段标识 - 可见 private final static String ATTACH_ENTRY2 = "xxx"; private final static String ATTACH_FIELD2 = "xxx"; private final static String ATTACH_FIELD2_TEXT = "xxx"; // 根据需要扩展补充
配置事件监听,文本控件的按钮点击以及分录下的超链接点击监听:
@Override public void registerListener(EventObject e) { super.registerListener(e); TextEdit textEdit = getControl(ATTACH_FIELD_TEXT); textEdit.addButtonClickListener(this); EntryGrid entryGrid = getControl(ATTACH_ENTRY); entryGrid.addHyperClickListener(this); }
监听事件的处理逻辑,在click方法打开附件上传页面,在hyperLinkClick方法判断预览或下载附件:
@Override public void click(EventObject evt) { super.click(evt); Control source = (Control)evt.getSource(); if (ATTACH_FIELD_TEXT.equals(source.getKey())) { AttachmentEdit attachmentEdit = getControl(ATTACH_FIELD); EntryGrid entryGrid = this.getView().getControl(ATTACH_ENTRY); int focusRow = entryGrid.getEntryState().getFocusRow(); String attachName = (String) getModel().getValue(ATTACH_FIELD_TEXT, focusRow); if (StringUtils.isBlank(attachName)){ // todo 附件字段批量删除是异步操作,频繁删除可能出现残留数据 DynamicObjectCollection attachDOs = getEntryRowAttachDOs(ATTACH_FIELD, ATTACH_ENTRY, focusRow); if (CollectionUtils.isNotEmpty(attachDOs)) { attachDOs.removeIf(attachDO-> attachDO.get("fbasedataid") == null); } } attachmentEdit.openEntryUploadView(focusRow, false); } }
@Override public void hyperLinkClick(HyperLinkClickEvent hyperLinkClickEvent) { if (ATTACH_FIELD_TEXT.equals(hyperLinkClickEvent.getFieldName())) { // 链接点击事件,字段需要勾选"显示为超链"选项,且锁定 DynamicObjectCollection attachDOs = getEntryRowAttachDOs(ATTACH_FIELD, ATTACH_ENTRY, hyperLinkClickEvent.getRowIndex()); if(CollectionUtils.isEmpty(attachDOs)){ getView().showTipNotification(ResManager.LoadKDString("未找到附件记录,附件可能已被删除!", "AttachFieldEntryEditPlugin_1")); return; } // 获取第一个附件 DynamicObject attachDO = (DynamicObject) attachDOs.get(0).get("fbasedataid"); String downloadUrl = UrlService.getAttachmentDownloadUrl(attachDO.getString("url")); String previewUrl = UrlService.getAttachmentPreviewUrl(attachDO.getString("url")); // 是否支持预览 Map<String, Object> checkPreviewArg = new HashMap<>(2); checkPreviewArg.put("type", attachDO.get("type")); checkPreviewArg.put("size", attachDO.get("size")); if (!AttachmentServiceHelper.ablePreView(checkPreviewArg)) { getView().download(downloadUrl); return; } // PC预览,6.0.1 无法新页签打开以及下载,6.0.2 已修复 Map<String, Object> previewArgs = new HashMap<>(); String[] KeyMapping = new String[]{ "type", "uid", "name", "size" }; for (String key : KeyMapping) { String[] kv = key.split("\\.", 2); previewArgs.put(kv[0], attachDO.get(kv[kv.length - 1])); } previewArgs.put("previewurl", previewUrl); previewArgs.put("url", downloadUrl); previewArgs.put("pageId", getView().getPageId()); previewArgs.put("status", "success"); getView().previewAttachment(previewArgs); } }
最后需要对文本内容特殊处理,联动附件删除以及文本值更新。
@Override public void propertyChanged(PropertyChangedArgs e) { super.propertyChanged(e); String name = e.getProperty().getName(); if (ATTACH_FIELD.equals(name)) { // 更新显示文件名称 ChangeData changeData = e.getChangeSet()[0]; int rowIndex = changeData.getRowIndex(); DynamicObjectCollection attachDOs = getEntryRowAttachDOs(ATTACH_FIELD, ATTACH_ENTRY, rowIndex); if (CollectionUtils.isEmpty(attachDOs)) { getModel().setValue(ATTACH_FIELD_TEXT, "", rowIndex); } else { DynamicObject attachDO = (DynamicObject) attachDOs.get(0).get("fbasedataid"); getModel().setValue(ATTACH_FIELD_TEXT, attachDO.getLocaleString("name").getLocaleValue(), rowIndex); } } else if (ATTACH_FIELD_TEXT.equals(name)) { ChangeData changeData = e.getChangeSet()[0]; if(StringUtils.isBlank(changeData.getNewValue())){ // 清除文本时删除文件 int rowIndex = changeData.getRowIndex(); DynamicObjectCollection attachDOs = getEntryRowAttachDOs(ATTACH_FIELD, ATTACH_ENTRY, rowIndex); if (CollectionUtils.isEmpty(attachDOs)) { return; } Map<String, Object> map = new HashMap<>(3); map.put("rowIndex", rowIndex); map.put("oldValue", changeData.getOldValue()); if (attachDOs.size() > 1) { DynamicObject attachDO = (DynamicObject) attachDOs.get(1).get("fbasedataid"); map.put("nextValue", attachDO.getLocaleString("name").getLocaleValue()); } String customValue = SerializationUtils.toJsonString(map); getView().showConfirm(ResManager.LoadKDString("文件删除后将无法恢复,确定删除该文件?", "AttachFieldEntryEditPlugin_0"), null, MessageBoxOptions.YesNo, null, new ConfirmCallBackListener("remove_attach_callback", this), null, customValue); } } }
@Override public void confirmCallBack(MessageBoxClosedEvent evt) { if ("remove_attach_callback".equals(evt.getCallBackId())) { String customValue = evt.getCustomVaule(); Map<String, Object> map = SerializationUtils.fromJsonString(customValue, Map.class); Object oldValue = map.get("oldValue"); Object nextValue = map.get("nextValue"); Integer rowIndex = (Integer)map.get("rowIndex"); if (MessageBoxResult.Yes.equals(evt.getResult())) { this.removeEntryAttach(ATTACH_FIELD, ATTACH_ENTRY, rowIndex); if (nextValue != null) { // 多附件的情况刷新文本 getModel().setValue(ATTACH_FIELD_TEXT, nextValue, rowIndex); } } else { getModel().setValue(ATTACH_FIELD_TEXT, oldValue); } } }
其中两个方法,分别处理获取分录附件数据以及删除附件字段的附件操作,这里仅以单据体为例实现,删除方法这里仅处理第一个附件,可根据实际需求调整处理。
/** * 获取分录行(单据体)附件字段对象 * @param attachKey 附件字段标识 * @param entryKey 单据体标识 * @param rowIndex 分录行号 * @return 附件字段实体对象 * @see kd.bos.metadata.entity.commonfield.AttachmentField */ @Nullable private DynamicObjectCollection getEntryRowAttachDOs(String attachKey, String entryKey, int rowIndex) { log.debug("getEntryRowAttachDOs[attachKey: {}, entryKey: {}, rowIndex: {}]", attachKey, entryKey, rowIndex); if (rowIndex >= getModel().getEntryRowCount(entryKey)) { log.debug("rowIndex > entryRowCount"); return null; } if (rowIndex < 0) { EntryGrid entryGrid = getView().getControl(entryKey); rowIndex = entryGrid.getEntryState().getFocusRow(); } if (rowIndex < 0) { log.debug("rowIndex < 0"); return null; } log.debug("rowIndex: {}", rowIndex); return (DynamicObjectCollection)getModel().getValue(attachKey, rowIndex); } /** * 删除分录行(单据体)第一个附件 * @param attachKey 附件字段标识 * @param entryKey 单据体标识 * @param rowIndex 分录行号 */ private void removeEntryAttach(String attachKey, String entryKey, int rowIndex) { DynamicObjectCollection attachDOs = getEntryRowAttachDOs(attachKey, entryKey, rowIndex); if (CollectionUtils.isEmpty(attachDOs)){ return; } AttachmentEdit attachmentEdit = getControl(attachKey); DynamicObject attachDO = (DynamicObject) attachDOs.get(0).get("fbasedataid"); DynamicObject entryRow = getModel().getEntryRowEntity(entryKey, rowIndex); AttachmentFieldServiceHelper.batchRemoveAtt( getModel().getDataEntityType().getName(), // 单据标识 attachmentEdit.getTableName(), // 附件字段关联表表名 new Object[]{attachDO.get("id")}, // 附件主键id new Object[]{entryRow.get("id")}, // 单据体分录主键id AttachmentRemoveSource.ENTRY, // 标记来源单据体 false ); attachDOs.remove(0); }
下面是实际效果演示,打开单据可以看到配置好了分录附件文本字段
点击文本框右边的按钮即可打开附件上传的表单上传附件
附件上传完成后返回单据,会更新分录文本字段显示为上传的附件名称
点击分录附件超链接即可查看附件,不支持查看的附件会触发浏览器下载
点击文本控件的叉号就会提示是否删除文件,确认后就会删除附件记录并清空文本
插件代码源文件见附件
3 注意事项
附件删除使用工具类的removeAttachments方法会有问题,使用了批处理方法是异步线程操作,不建议频繁调用
附件代码预览在6.0.1版本无法使用新页签打开和下载按钮,前端在6.0.2版本修复
4 环境版本
金蝶云·苍穹 6.0
5 相关资料
如何在单据分录平铺展示附件
本文2024-09-23 00:36:29发表“云苍穹知识”栏目。
本文链接:https://wenku.my7c.com/article/kingdee-cangqiong-140558.html