Переглянути джерело

知识库,添加视频链接功能

徐滕 5 днів тому
батько
коміт
158bd46a59

+ 31 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBaseShareLinkDao.java

@@ -0,0 +1,31 @@
+package com.jeeplus.modules.WorkKnowledgeBase.dao;
+
+import com.jeeplus.common.persistence.CrudDao;
+import com.jeeplus.common.persistence.annotation.MyBatisDao;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseShareLink;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 知识库文件关联链接 DAO
+ * @author system
+ * @version 2026-09-02
+ */
+@MyBatisDao
+public interface WorkKnowledgeBaseShareLinkDao extends CrudDao<WorkKnowledgeBaseShareLink> {
+
+    /**
+     * 根据文件ID查询关联链接列表
+     * @param fileId 文件ID
+     * @return 链接列表(按排序号排序)
+     */
+    List<WorkKnowledgeBaseShareLink> findByFileId(@Param("fileId") String fileId);
+
+    /**
+     * 逻辑删除文件的所有关联链接
+     * @param fileId 文件ID
+     * @return 影响的记录数
+     */
+    int deleteByFileId(@Param("fileId") String fileId);
+}

+ 12 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBaseShareInfo.java

@@ -3,6 +3,7 @@ package com.jeeplus.modules.WorkKnowledgeBase.entity;
 import com.jeeplus.common.persistence.DataEntity;
 import com.jeeplus.modules.sys.entity.Workattachment;
 
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -101,6 +102,9 @@ public class WorkKnowledgeBaseShareInfo extends DataEntity<WorkKnowledgeBaseShar
     /** 所属人部门名称(展示字段,非数据库字段) */
     private String ownerOfficeName;
 
+    /** 关联链接列表(非数据库字段,用于表单提交和详情展示) */
+    private List<WorkKnowledgeBaseShareLink> shareLinks = new ArrayList<>();
+
     public String getTreeNodeId() {
         return treeNodeId;
     }
@@ -372,4 +376,12 @@ public class WorkKnowledgeBaseShareInfo extends DataEntity<WorkKnowledgeBaseShar
     public void setOwnerOfficeName(String ownerOfficeName) {
         this.ownerOfficeName = ownerOfficeName;
     }
+
+    public List<WorkKnowledgeBaseShareLink> getShareLinks() {
+        return shareLinks;
+    }
+
+    public void setShareLinks(List<WorkKnowledgeBaseShareLink> shareLinks) {
+        this.shareLinks = shareLinks;
+    }
 }

+ 58 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBaseShareLink.java

@@ -0,0 +1,58 @@
+package com.jeeplus.modules.WorkKnowledgeBase.entity;
+
+import com.jeeplus.common.persistence.DataEntity;
+
+/**
+ * 知识库文件关联链接 Entity
+ * @author system
+ * @version 2026-09-02
+ */
+public class WorkKnowledgeBaseShareLink extends DataEntity<WorkKnowledgeBaseShareLink> {
+
+    private static final long serialVersionUID = 1L;
+
+    private String fileId;        // 关联文件ID(work_knowledge_base_share_info.id)
+    private String linkDesc;      // 链接描述
+    private String linkUrl;       // 链接地址
+    private Integer sortOrder;    // 排序号
+
+    public WorkKnowledgeBaseShareLink() {
+        super();
+    }
+
+    public WorkKnowledgeBaseShareLink(String id) {
+        super(id);
+    }
+
+    public String getFileId() {
+        return fileId;
+    }
+
+    public void setFileId(String fileId) {
+        this.fileId = fileId;
+    }
+
+    public String getLinkDesc() {
+        return linkDesc;
+    }
+
+    public void setLinkDesc(String linkDesc) {
+        this.linkDesc = linkDesc;
+    }
+
+    public String getLinkUrl() {
+        return linkUrl;
+    }
+
+    public void setLinkUrl(String linkUrl) {
+        this.linkUrl = linkUrl;
+    }
+
+    public Integer getSortOrder() {
+        return sortOrder;
+    }
+
+    public void setSortOrder(Integer sortOrder) {
+        this.sortOrder = sortOrder;
+    }
+}

+ 165 - 20
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBaseShareService.java

@@ -139,6 +139,9 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
     @Autowired
     private WorkKnowledgeBaseClickRecordDao clickRecordDao;
 
+    @Autowired
+    private WorkKnowledgeBaseShareLinkDao shareLinkDao;
+
     /**
      * 根据文件id加载附件列表(编辑表单回显用)
      */
@@ -160,6 +163,16 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
     }
 
     /**
+     * 根据文件id加载关联链接列表(编辑表单回显/详情展示用)
+     */
+    public List<WorkKnowledgeBaseShareLink> findLinksByFileId(String fileId) {
+        if (StringUtils.isBlank(fileId)) {
+            return new ArrayList<>();
+        }
+        return shareLinkDao.findByFileId(fileId);
+    }
+
+    /**
      * 根据文件id查询动态字段値列表(编辑表单回显用)
      */
     public List<WorkKnowledgeBaseDynamicValueInfo> findDynamicValuesByFileId(String fileId) {
@@ -427,20 +440,26 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
         }
         
         boolean hasValidAttachment = hasValidAttachment(attachments);
-        // 调试日志:输出附件判定结果
-        logger.info("saveShare附件判定 - isNew:" + isNew + ", attachments:" + (attachments != null ? attachments.size() : "null")
+        // 同时判断是否有有效链接(链接与附件等价,有链接也算非草稿)
+        List<WorkKnowledgeBaseShareLink> shareLinks = shareInfo.getShareLinks();
+        boolean hasValidLink = hasValidLink(shareLinks);
+        // 编辑时,若前端未提交链接,回查DB已有链接进行判定
+        if (!isNew && (shareLinks == null || shareLinks.isEmpty()) && StringUtils.isNotBlank(shareInfo.getId())) {
+            List<WorkKnowledgeBaseShareLink> existingLinks = findLinksByFileId(shareInfo.getId());
+            hasValidLink = hasValidLink(existingLinks);
+        }
+        // 有附件 OR 有链接 -> 视为有内容,进入审核流
+        boolean hasContent = hasValidAttachment || hasValidLink;
+        logger.info("saveShare内容判定 - isNew:" + isNew
                 + ", hasValidAttachment:" + hasValidAttachment
+                + ", hasValidLink:" + hasValidLink
+                + ", hasContent:" + hasContent
                 + ", treeNodeId:" + shareInfo.getTreeNodeId());
-        if (attachments != null) {
-            for (int idx = 0; idx < attachments.size(); idx++) {
-                Workattachment att = attachments.get(idx);
-                logger.info("  attachment[" + idx + "] id='" + att.getId() + "', url='" + att.getUrl() + "', delFlag='" + att.getDelFlag() + "'");
-            }
-        }
         // 编辑时,若前端未提交附件列表(用户未更换文件),回查DB已有附件进行判定
         if (!isNew && (attachments == null || attachments.isEmpty()) && StringUtils.isNotBlank(shareInfo.getId())) {
             List<Workattachment> existingAttachments = findAttachmentsByFileId(shareInfo.getId());
             hasValidAttachment = hasValidAttachment(existingAttachments);
+            hasContent = hasValidAttachment || hasValidLink;
         }
         
         if (isQaCategory) {
@@ -456,12 +475,12 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
             shareInfo.setAuditRejectCount(0);
         } else {
             // ========== 非问答分类:走原有审核流程 ==========
-            // 典型案例分类也走正常审核流程,根据有无附件判断状态
-            // 管理员上传时,有附件直接审核通过(5),无需审核
+            // 典型案例分类也走正常审核流程,根据有无内容(附件或链接)判断状态
+            // 管理员上传时,有内容直接审核通过(5),无需审核
             if (isNew) {
-                // 新增:无文件草稿0,有文件未审核 1(管理员直接审核通过5)
+                // 新增:无内容草稿0,有内容(附件或链接)未审核 1(管理员直接审核通过5)
                 String newAuditStatus;
-                if (hasValidAttachment) {
+                if (hasContent) {
                     newAuditStatus = isAdmin ? AUDIT_STATUS_PASSED : AUDIT_STATUS_PENDING;
                 } else {
                     newAuditStatus = AUDIT_STATUS_DRAFT;
@@ -469,8 +488,8 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
                 shareInfo.setAuditStatus(newAuditStatus);
                 shareInfo.setAuditPassCount(0);
                 shareInfo.setAuditRejectCount(0);
-                // 有附件且非管理员(走审核流程)时记录提交审核人为当前登录人,同时初始化本轮审核起始时间
-                if (hasValidAttachment && !isAdmin) {
+                // 有内容且非管理员(走审核流程)时记录提交审核人为当前登录人
+                if (hasContent && !isAdmin) {
                     shareInfo.setSubmitAuditUserId(UserUtils.getUser().getId());
                     shareInfo.setAuditStartDate(new Date());
                 }
@@ -488,7 +507,7 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
                 // currentStatus为null时视为未初始化,允许重新判定;草稿(0)和审核未通过(4)也允许重新判定
                 if (currentStatus == null || currentStatus.equals(AUDIT_STATUS_DRAFT) || currentStatus.equals(AUDIT_STATUS_REJECTED)) {
                     String editAuditStatus;
-                    if (hasValidAttachment) {
+                    if (hasContent) {
                         editAuditStatus = isAdmin ? AUDIT_STATUS_PASSED : AUDIT_STATUS_PENDING;
                     } else {
                         editAuditStatus = AUDIT_STATUS_DRAFT;
@@ -497,8 +516,8 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
                     logger.info("状态重新判定 - 新auditStatus:'" + shareInfo.getAuditStatus() + "', isAdmin:" + isAdmin);
                     shareInfo.setAuditPassCount(0);
                     shareInfo.setAuditRejectCount(0);
-                    // 重新提交且非管理员(走审核流程)时更新提交审核人为当前登录人,并刷新本轮审核起始时间
-                    if (hasValidAttachment && !isAdmin) {
+                    // 重新提交且非管理员(走审核流程)时更新提交审核人为当前登录人
+                    if (hasContent && !isAdmin) {
                         shareInfo.setSubmitAuditUserId(UserUtils.getUser().getId());
                         shareInfo.setAuditStartDate(new Date());
                     }
@@ -536,7 +555,25 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
                 }
             }
         }
-        
+
+        // 保存关联链接(先删后插)
+        if (StringUtils.isNotBlank(shareInfo.getId())) {
+            shareLinkDao.deleteByFileId(shareInfo.getId());
+            if (shareLinks != null && !shareLinks.isEmpty()) {
+                int order = 0;
+                for (WorkKnowledgeBaseShareLink link : shareLinks) {
+                    if (StringUtils.isBlank(link.getLinkDesc()) || StringUtils.isBlank(link.getLinkUrl())) {
+                        continue; // 跳过无效链接
+                    }
+                    link.setId(IdGen.uuid());
+                    link.setFileId(shareInfo.getId());
+                    link.setSortOrder(order++);
+                    link.preInsert();
+                    shareLinkDao.insert(link);
+                }
+            }
+        }
+
         // 先逻辑删除旧的动态字段値
         WorkKnowledgeBaseDynamicValueInfo delEntity = new WorkKnowledgeBaseDynamicValueInfo();
         delEntity.setFileId(shareInfo.getId());
@@ -766,6 +803,21 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
     }
 
     /**
+     * 判断链接列表中是否存在有效链接(描述和URL均不为空)
+     */
+    private boolean hasValidLink(List<WorkKnowledgeBaseShareLink> links) {
+        if (links == null || links.isEmpty()) {
+            return false;
+        }
+        for (WorkKnowledgeBaseShareLink link : links) {
+            if (StringUtils.isNotBlank(link.getLinkDesc()) && StringUtils.isNotBlank(link.getLinkUrl())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * 批量填充每行数据的第一个附件信息(替代原有 file_url/file_name 字段)
      * 从 work_attachment 表查询每个文件对应的第一个附件,将 url 和 attachmentName 写入 map
      * @param list 查询结果列表(每行为 Map,包含 id 字段)
@@ -778,14 +830,46 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
             Object id = map.get("id");
             if (id == null) continue;
             String fileId = id.toString();
+            
+            // 查询附件
             List<Workattachment> attList = workattachmentService.getListByAttachmentIdAndFlag(fileId, ATTACHMENT_FLAG);
+            
+            // 查询链接
+            List<WorkKnowledgeBaseShareLink> links = shareLinkDao.findByFileId(fileId);
+            
+            // 构建统一的内容项列表(用于列表页展示)
+            List<Map<String, String>> contentItems = new ArrayList<>();
+            
+            // 添加附件项
+            if (attList != null && !attList.isEmpty()) {
+                for (Workattachment att : attList) {
+                    Map<String, String> item = new HashMap<>();
+                    item.put("type", "file");
+                    item.put("name", att.getAttachmentName() != null ? att.getAttachmentName() : "");
+                    item.put("url", att.getUrl() != null ? att.getUrl() : "");
+                    item.put("temporaryUrl", att.getTemporaryUrl() != null ? att.getTemporaryUrl() : "");
+                    item.put("uploadMode", att.getUploadMode() != null ? att.getUploadMode().toString() : "1");
+                    contentItems.add(item);
+                }
+            }
+            
+            // 添加链接项
+            if (links != null && !links.isEmpty()) {
+                for (WorkKnowledgeBaseShareLink link : links) {
+                    Map<String, String> item = new HashMap<>();
+                    item.put("type", "link");
+                    item.put("name", link.getLinkDesc() != null ? link.getLinkDesc() : "");
+                    item.put("url", link.getLinkUrl() != null ? link.getLinkUrl() : "");
+                    contentItems.add(item);
+                }
+            }
+            
+            // 填充基础字段(兼容原有逻辑)
             if (attList != null && !attList.isEmpty()) {
                 Workattachment firstAttach = attList.get(0);
                 map.put("fileUrl", firstAttach.getUrl());
                 map.put("fileName", firstAttach.getAttachmentName());
-                // 附件数量
                 map.put("attachmentCount", attList.size());
-                // 所有附件名称逗号分隔展示
                 StringBuilder namesBuilder = new StringBuilder();
                 for (int i = 0; i < attList.size(); i++) {
                     if (i > 0) namesBuilder.append(", ");
@@ -794,11 +878,66 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
                 }
                 map.put("allAttachmentNames", namesBuilder.toString());
             } else {
+                map.put("fileUrl", "");
+                map.put("fileName", "");
                 map.put("attachmentCount", 0);
                 map.put("allAttachmentNames", "");
             }
+            
+            // 填充链接基础字段(兼容原有逻辑)
+            map.put("linkCount", links != null ? links.size() : 0);
+            if (links != null && !links.isEmpty()) {
+                StringBuilder linkJson = new StringBuilder("[");
+                for (int i = 0; i < links.size(); i++) {
+                    if (i > 0) linkJson.append(",");
+                    linkJson.append("{\"desc\":\"")
+                            .append(links.get(i).getLinkDesc() != null ? links.get(i).getLinkDesc().replace("\"", "\\\"") : "")
+                            .append("\",\"url\":\"")
+                            .append(links.get(i).getLinkUrl() != null ? links.get(i).getLinkUrl().replace("\"", "\\\"") : "")
+                            .append("\"}");
+                }
+                linkJson.append("]");
+                map.put("linkData", linkJson.toString());
+            } else {
+                map.put("linkData", "[]");
+            }
+            
+            // 填充统一内容项列表(JSON格式,供新展示逻辑使用)
+            if (!contentItems.isEmpty()) {
+                StringBuilder contentJson = new StringBuilder("[");
+                for (int i = 0; i < contentItems.size(); i++) {
+                    if (i > 0) contentJson.append(",");
+                    Map<String, String> item = contentItems.get(i);
+                    contentJson.append("{\"type\":\"").append(item.get("type")).append("\"")
+                            .append(",\"name\":\"").append(escapeJson(item.get("name"))).append("\"")
+                            .append(",\"url\":\"").append(escapeJson(item.get("url"))).append("\"");
+                    if (item.containsKey("temporaryUrl")) {
+                        contentJson.append(",\"temporaryUrl\":\"").append(escapeJson(item.get("temporaryUrl"))).append("\"");
+                    }
+                    if (item.containsKey("uploadMode")) {
+                        contentJson.append(",\"uploadMode\":\"").append(item.get("uploadMode")).append("\"");
+                    }
+                    contentJson.append("}");
+                }
+                contentJson.append("]");
+                map.put("contentItems", contentJson.toString());
+            } else {
+                map.put("contentItems", "[]");
+            }
         }
     }
+    
+    /**
+     * JSON字符串转义
+     */
+    private String escapeJson(String value) {
+        if (value == null) return "";
+        return value.replace("\\", "\\\\")
+                    .replace("\"", "\\\"")
+                    .replace("\n", "\\n")
+                    .replace("\r", "\\r")
+                    .replace("\t", "\\t");
+    }
 
     /**
      * 发起审核:未审核(1) -> 审核中(2),清空通过/驳回统计
@@ -1882,6 +2021,9 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
                 workattachmentService.delete(attachment);
             }
         }
+
+        // 同步逻辑删除关联链接
+        shareLinkDao.deleteByFileId(entity.getId());
     }
 
     /**
@@ -2589,6 +2731,9 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
             // 加载附件列表(如果需要)
             question.setWorkAttachments(findAttachmentsByFileId(questionId));
             
+            // 加载关联链接列表
+            question.setShareLinks(findLinksByFileId(questionId));
+            
             // 加载动态字段值(如果需要)
             List<WorkKnowledgeBaseDynamicValueInfo> dynamicValues = findDynamicValuesByFileId(questionId);
             if (dynamicValues != null && !dynamicValues.isEmpty()) {

+ 17 - 1
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBaseShareController.java

@@ -228,6 +228,8 @@ public class WorkKnowledgeBaseShareController extends BaseController {
         // 加载附件列表
         if (StringUtils.isNotBlank(shareInfo.getId())) {
             shareInfo.setWorkAttachments(shareService.findAttachmentsByFileId(shareInfo.getId()));
+            // 加载关联链接列表
+            shareInfo.setShareLinks(shareService.findLinksByFileId(shareInfo.getId()));
         }
 
         model.addAttribute("entity", shareInfo);
@@ -414,6 +416,8 @@ public class WorkKnowledgeBaseShareController extends BaseController {
         // 编辑时加载已有附件列表(回显用)
         if (StringUtils.isNotBlank(shareInfo.getId())) {
             shareInfo.setWorkAttachments(shareService.findAttachmentsByFileId(shareInfo.getId()));
+            // 加载关联链接列表(回显用)
+            shareInfo.setShareLinks(shareService.findLinksByFileId(shareInfo.getId()));
         }
 
         // 编辑时回显主审专家姓名(expertReviewerNames 为非数据库字段,需根据ID反查)
@@ -484,7 +488,11 @@ public class WorkKnowledgeBaseShareController extends BaseController {
             addMessage(model, "该问题已确认答案,无法重复操作!");
             return "error";
         }
-        
+
+        // 加载附件和关联链接列表
+        shareInfo.setWorkAttachments(shareService.findAttachmentsByFileId(id));
+        shareInfo.setShareLinks(shareService.findLinksByFileId(id));
+
         model.addAttribute("entity", shareInfo);
         return "modules/WorkKnowledgeBase/workKnowledgeBaseConfirmAnswer";
     }
@@ -498,6 +506,7 @@ public class WorkKnowledgeBaseShareController extends BaseController {
     @RequestMapping(value = "save")
     public String save(WorkKnowledgeBaseShareInfo shareInfo,
                        @RequestParam(required = false) String dynamicValuesJson,
+                       @RequestParam(required = false) String shareLinksJson,
                        Model model, RedirectAttributes redirectAttributes) {
         if (!beanValidator(model, shareInfo)) {
             return form(shareInfo, model);
@@ -543,6 +552,13 @@ public class WorkKnowledgeBaseShareController extends BaseController {
                         JSON.parseArray(dynamicValuesJson, WorkKnowledgeBaseDynamicValueInfo.class);
                 shareInfo.setDynamicValues(dynamicValues);
             }
+            // 解析关联链接
+            if (StringUtils.isNotBlank(shareLinksJson)) {
+                shareLinksJson = shareLinksJson.replace("&quot;", "\"");
+                List<WorkKnowledgeBaseShareLink> shareLinks =
+                        JSON.parseArray(shareLinksJson, WorkKnowledgeBaseShareLink.class);
+                shareInfo.setShareLinks(shareLinks);
+            }
             shareService.saveShare(shareInfo);
             addMessage(redirectAttributes, "保存知识库文件信息成功");
         } catch (Exception e) {

+ 4 - 1
src/main/java/com/jeeplus/modules/ruralprojectrecords/web/RuralProjectSignatureOldMessageDisposeController.java

@@ -1376,10 +1376,13 @@ public class RuralProjectSignatureOldMessageDisposeController extends BaseContro
                     break;
                 }
 
-                // 模拟阅读(如果该文档在已阅读列表中且用户未阅读过
+                // 模拟阅读(如果该文档在已阅读列表中)
                 if (readDocIds.contains(fileId)) {
                     WorkKnowledgeBaseReadRecord existingRead = readRecordDao.findByFileIdAndUserId(fileId, userId);
                     if (existingRead == null) {
+                        // 点击量每次阅读都+1(活跃度指标,不计入积分)
+                        shareInfoDao.incrementClickCount(fileId);
+                        // 首次阅读:记录阅读 + 发放积分
                         readLikeService.recordRead(fileId, userId);
                         readCount++;
                         readPoints += readPointValue;

+ 4 - 0
src/main/java/com/jeeplus/modules/workprojectnotify/web/WorkProjectNotifyController.java

@@ -11613,6 +11613,8 @@ public class WorkProjectNotifyController extends BaseController {
 		// 加载附件列表
 		if (StringUtils.isNotBlank(shareInfo.getId())) {
 			shareInfo.setWorkAttachments(workKnowledgeBaseShareService.findAttachmentsByFileId(shareInfo.getId()));
+			// 加载关联链接列表
+			shareInfo.setShareLinks(workKnowledgeBaseShareService.findLinksByFileId(shareInfo.getId()));
 		}
 
 		model.addAttribute("entity", shareInfo);
@@ -11738,6 +11740,8 @@ public class WorkProjectNotifyController extends BaseController {
 		// 加载附件列表
 		if (StringUtils.isNotBlank(shareInfo.getId())) {
 			shareInfo.setWorkAttachments(workKnowledgeBaseShareService.findAttachmentsByFileId(shareInfo.getId()));
+			// 加载关联链接列表
+			shareInfo.setShareLinks(workKnowledgeBaseShareService.findLinksByFileId(shareInfo.getId()));
 		}
 
 		model.addAttribute("entity", shareInfo);

+ 25 - 7
src/main/java/com/jeeplus/modules/workstaff/utils/PdfSignUtil.java

@@ -249,22 +249,40 @@ public class PdfSignUtil {
         ImageIO.write(img, "png", new File(outPath));
     }
 
-    // 精准采样:只取签字中心实心墨迹单点原色
+    // 精准采样:在签字中心区域内找最暗的非白色像素,避免采到白色背景或抗锯齿边缘
     private static int getSolidSignColor(BufferedImage img) {
         int midX = img.getWidth() / 2;
         int midY = img.getHeight() / 2;
+        int darkestArgb = 0;
+        int minLuminance = Integer.MAX_VALUE;
+        boolean found = false;
+
         for (int dy = -8; dy <= 8; dy++) {
             for (int dx = -8; dx <= 8; dx++) {
                 int x = midX + dx;
                 int y = midY + dy;
-                if(x < 0 || y <0 || x >= img.getWidth() || y >= img.getHeight()) continue;
-                int argb = img.getRGB(x,y);
-                int alpha = (argb >>24) & 0xFF;
-                if(alpha > 220){
-                    return argb;
+                if (x < 0 || y < 0 || x >= img.getWidth() || y >= img.getHeight()) continue;
+                int argb = img.getRGB(x, y);
+                int alpha = (argb >> 24) & 0xFF;
+                if (alpha < 200) continue; // 跳过透明/半透明像素
+
+                int r = (argb >> 16) & 0xFF;
+                int g = (argb >> 8) & 0xFF;
+                int b = argb & 0xFF;
+
+                // 过滤近白色背景(RGB都>230视为白色)
+                if (r > 230 && g > 230 && b > 230) continue;
+
+                // 计算亮度,找最暗的像素(即真正的墨迹颜色)
+                int luminance = r + g + b;
+                if (luminance < minLuminance) {
+                    minLuminance = luminance;
+                    // 保留原始RGB,alpha强制设为不透明
+                    darkestArgb = 0xFF000000 | (r << 16) | (g << 8) | b;
+                    found = true;
                 }
             }
         }
-        return 0xFF000000;
+        return found ? darkestArgb : 0xFF000000;
     }
 }

+ 48 - 0
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBaseShareLinkDao.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeBaseShareLinkDao">
+
+    <sql id="shareLinkColumns">
+        a.id AS "id",
+        a.file_id AS "fileId",
+        a.link_desc AS "linkDesc",
+        a.link_url AS "linkUrl",
+        a.sort_order AS "sortOrder",
+        a.create_by AS "createBy.id",
+        a.create_date AS "createDate",
+        a.update_by AS "updateBy.id",
+        a.update_date AS "updateDate",
+        a.remarks AS "remarks",
+        a.del_flag AS "delFlag"
+    </sql>
+
+    <!-- 根据文件ID查询关联链接列表 -->
+    <select id="findByFileId" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseShareLink">
+        SELECT
+            <include refid="shareLinkColumns"/>
+        FROM work_knowledge_base_share_link a
+        WHERE a.file_id = #{fileId}
+          AND a.del_flag = '0'
+        ORDER BY a.sort_order ASC, a.create_date ASC
+    </select>
+
+    <!-- 插入链接 -->
+    <insert id="insert">
+        INSERT INTO work_knowledge_base_share_link (
+            id, file_id, link_desc, link_url, sort_order,
+            create_by, create_date, update_by, update_date, remarks, del_flag
+        ) VALUES (
+            #{id}, #{fileId}, #{linkDesc}, #{linkUrl}, #{sortOrder},
+            #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{remarks}, '0'
+        )
+    </insert>
+
+    <!-- 逻辑删除文件的所有关联链接 -->
+    <update id="deleteByFileId">
+        UPDATE work_knowledge_base_share_link
+        SET del_flag = '1', update_date = NOW()
+        WHERE file_id = #{fileId}
+          AND del_flag = '0'
+    </update>
+
+</mapper>

+ 20 - 0
src/main/resources/mysqlDateBase/alter_work_knowledge_base_share_link.sql

@@ -0,0 +1,20 @@
+-- 知识库文件关联链接表
+-- 用于存储知识库文件关联的外部链接(描述+URL),与附件(work_attachment)平行,可同时存在
+-- @author system
+-- @version 2026-09-02
+
+CREATE TABLE IF NOT EXISTS work_knowledge_base_share_link (
+    id              VARCHAR(64)     NOT NULL    COMMENT '主键ID',
+    file_id         VARCHAR(64)     NOT NULL    COMMENT '关联文件ID(work_knowledge_base_share_info.id)',
+    link_desc       VARCHAR(500)    NOT NULL    COMMENT '链接描述',
+    link_url        VARCHAR(2000)   NOT NULL    COMMENT '链接地址',
+    sort_order      INT             DEFAULT 0   COMMENT '排序号',
+    create_by       VARCHAR(64)                 COMMENT '创建者ID',
+    create_date     DATETIME                    COMMENT '创建时间',
+    update_by       VARCHAR(64)                 COMMENT '更新者ID',
+    update_date     DATETIME                    COMMENT '更新时间',
+    remarks         VARCHAR(500)                COMMENT '备注',
+    del_flag        CHAR(1)         DEFAULT '0' COMMENT '删除标记(0正常 1删除)',
+    PRIMARY KEY (id),
+    INDEX idx_file_id (file_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库文件关联链接表';

+ 29 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseConfirmAnswer.jsp

@@ -146,6 +146,35 @@
                     </div>
                 </div>
             </c:if>
+
+            <!-- 相关链接展示 -->
+            <c:if test="${not empty entity.shareLinks}">
+                <div class="form-group layui-row">
+                    <div class="form-group-label"><h2>相关链接</h2></div>
+                    <div class="layui-item layui-col-xs12" style="padding:0 16px;">
+                        <table class="table table-bordered table-condensed details">
+                            <thead>
+                            <tr>
+                                <th style="width: 40%;">链接描述</th>
+                                <th>链接地址</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            <c:forEach items="${entity.shareLinks}" var="link">
+                                <tr>
+                                    <td>${link.linkDesc}</td>
+                                    <td>
+                                        <a href="${link.linkUrl}" target="_blank" class="op-btn" style="color: #1E9FFF;">
+                                            <i class="fa fa-external-link"></i>&nbsp;${link.linkUrl}
+                                        </a>
+                                    </td>
+                                </tr>
+                            </c:forEach>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </c:if>
             
             <!-- 发表回答区域 -->
             <div class="form-group layui-row">

+ 29 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareDetail.jsp

@@ -447,6 +447,35 @@
             </div>
         </c:if>
 
+        <!-- 相关链接展示 -->
+        <c:if test="${not empty entity.shareLinks}">
+            <div class="form-group layui-row">
+                <div class="form-group-label"><h2>相关链接</h2></div>
+                <div class="layui-item layui-col-xs12" style="padding:0 16px;">
+                    <table class="table table-bordered table-condensed details">
+                        <thead>
+                        <tr>
+                            <th style="width: 40%;">链接描述</th>
+                            <th>链接地址</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        <c:forEach items="${entity.shareLinks}" var="link">
+                            <tr>
+                                <td>${link.linkDesc}</td>
+                                <td>
+                                    <a href="${link.linkUrl}" target="_blank" class="op-btn" style="color: #1E9FFF;">
+                                        <i class="fa fa-external-link"></i>&nbsp;${link.linkUrl}
+                                    </a>
+                                </td>
+                            </tr>
+                        </c:forEach>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </c:if>
+
 
 
         <!-- 审核意见区域(审核中和未审核状态下,非创建人可见,且非只读模式) -->

+ 80 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareForm.jsp

@@ -26,6 +26,8 @@
             if (validateForm.form()) {
                 // 提交前收集动态字段值为JSON
                 collectDynamicValues();
+                // 提交前收集关联链接为JSON
+                collectShareLinks();
                 // 设置 target 为 _self,让 redirect 结果只在弹窗 iframe 内加载,不影响父页面
                 $('#inputForm').attr('target', '_self');
                 $('#inputForm').submit();
@@ -178,6 +180,14 @@
             toggleExpertReviewer();
             });
 
+            // 初始化已有链接数据(修改时回显)
+            var existingLinks = ${fns:toJson(entity.shareLinks)};
+            if (existingLinks && existingLinks.length > 0) {
+                for (var i = 0; i < existingLinks.length; i++) {
+                    addShareLinkRow(existingLinks[i]);
+                }
+            }
+
         });
 
     /** 收集动态字段值为JSON数组,写入隐藏域 */
@@ -192,6 +202,40 @@
         $('#dynamicValuesJson').val(JSON.stringify(values));
     }
 
+    /** 相关链接行索引 */
+    var shareLinkRowIdx = 0;
+    var shareLinkTpl = "";
+
+    /** 添加一条链接行 */
+    function addShareLinkRow(row) {
+        if (!shareLinkTpl) {
+            shareLinkTpl = $("#shareLinkTpl").html().replace(/(\/\/\<\!\-\-)|(\/\/\-\->)/g, "");
+        }
+        $("#shareLinkList").append(Mustache.render(shareLinkTpl, {
+            idx: shareLinkRowIdx,
+            row: row || {linkDesc: '', linkUrl: ''}
+        }));
+        shareLinkRowIdx++;
+    }
+
+    /** 删除一条链接行 */
+    function delShareLinkRow(obj, prefix) {
+        $(prefix).remove();
+    }
+
+    /** 收集所有链接为JSON数组,写入隐藏域 */
+    function collectShareLinks() {
+        var links = [];
+        $("#shareLinkList tr").each(function() {
+            var desc = $(this).find("input[name$='.linkDesc']").val();
+            var url = $(this).find("input[name$='.linkUrl']").val();
+            if (desc && url) {
+                links.push({linkDesc: desc, linkUrl: url});
+            }
+        });
+        $('#shareLinksJson').val(JSON.stringify(links));
+    }
+
     </script>
 </head>
 <body>
@@ -204,6 +248,8 @@
             <form:hidden path="rootId"/>
             <!-- 动态字段值JSON隐藏域 -->
             <input type="hidden" id="dynamicValuesJson" name="dynamicValuesJson"/>
+            <!-- 关联链接JSON隐藏域 -->
+            <input type="hidden" id="shareLinksJson" name="shareLinksJson"/>
 
             <div class="form-group layui-row">
                 <c:choose>
@@ -381,6 +427,40 @@
                     storeAs="workKnowledgeBaseShareInfo"
             />
 
+            <!-- 相关链接区域(与附件可共存,支持多条) -->
+            <div class="form-group layui-row">
+                <div class="form-group-label"><h2>相关链接</h2></div>
+                <div class="layui-item nav-btns">
+                    <a class="nav-btn nav-btn-add" onclick="addShareLinkRow()" title="新增"><i class="fa fa-plus"></i> 新增</a>
+                </div>
+                <div class="layui-item layui-col-xs12 form-table-container">
+                    <table id="shareLinkTable" class="table table-bordered table-condensed can-edit">
+                        <thead>
+                        <tr>
+                            <th width="40%"><font color="red">*</font>链接描述</th>
+                            <th width="50%"><font color="red">*</font>链接地址</th>
+                            <th width="10%">操作</th>
+                        </tr>
+                        </thead>
+                        <tbody id="shareLinkList">
+                        </tbody>
+                    </table>
+                    <script type="text/template" id="shareLinkTpl">
+                        <tr id="shareLinkRow{{idx}}">
+                            <td>
+                                <input placeholder="请输入链接描述" id="shareLinkList{{idx}}_linkDesc" name="shareLinkList[{{idx}}].linkDesc" type="text" value="{{row.linkDesc}}" class="form-control required" maxlength="500"/>
+                            </td>
+                            <td>
+                                <input placeholder="请输入URL地址" id="shareLinkList{{idx}}_linkUrl" name="shareLinkList[{{idx}}].linkUrl" type="text" value="{{row.linkUrl}}" class="form-control required" maxlength="2000"/>
+                            </td>
+                            <td class="text-center op-td">
+                                <span class="op-btn op-btn-delete" onclick="delShareLinkRow(this, '#shareLinkRow{{idx}}')" title="删除"><i class="glyphicon glyphicon-remove"></i>&nbsp;删除</span>
+                            </td>
+                        </tr>
+                    </script>
+                </div>
+            </div>
+
             <div class="form-group layui-row page-end"></div>
         </form:form>
     </div>

+ 155 - 52
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareList.jsp

@@ -308,42 +308,82 @@
             });
         }
 
-        /** 多文件弹窗:展示所有附件列表 */
+        /** 附件/链接列表弹窗:展示所有文件和链接 */
         function showAttachmentList(fileId) {
+            // 从表格数据中获取 contentItems
+            var tableData = layui.table.cache['checkboxTable'];
+            var row = null;
+            for (var i = 0; i < tableData.length; i++) {
+                if (tableData[i].id === fileId) {
+                    row = tableData[i];
+                    break;
+                }
+            }
+            
+            if (!row) {
+                layer.msg('未找到数据', {icon: 0});
+                return;
+            }
+            
+            var items = [];
+            if (row.contentItems && row.contentItems !== '[]') {
+                try {
+                    items = JSON.parse(row.contentItems);
+                } catch(e) {}
+            }
+            
+            if (items.length === 0) {
+                layer.msg('暂无内容', {icon: 0});
+                return;
+            }
+            
+            // 构建表格HTML(参考详情页附件样式)
+            var html = '<div style="padding:15px;">';
+            html += '<table class="table table-bordered table-condensed details" style="margin:0;">';
+            html += '<thead><tr>';
+            html += '<th style="width:80px;">类型</th>';
+            html += '<th>名称/描述</th>';
+            html += '</tr></thead><tbody>';
+            
+            for (var i = 0; i < items.length; i++) {
+                var item = items[i];
+                var typeIcon = item.type === 'file' ? '<i class="fa fa-file" style="color:#1e9fff;"></i> 文件' : '<i class="fa fa-link" style="color:#5FB878;"></i> 链接';
+                var name = item.name || '';
+                var actionHtml = '';
+                
+                if (item.type === 'file') {
+                    // 文件:根据类型显示预览或下载
+                    var previewUrl = item.temporaryUrl || item.url || '';
+                    var fileName = name.toLowerCase();
+                    if (fileName.indexOf('jpg') != -1 || fileName.indexOf('png') != -1 || fileName.indexOf('gif') != -1 || fileName.indexOf('jpeg') != -1 || fileName.indexOf('bmp') != -1) {
+                        // 图片:点击预览
+                        actionHtml = '<a href="javascript:void(0)" class="attention-info" onclick="openPreviewWithRead(\'' + previewUrl + '\',5,\'' + fileId + '\')">' + name + '</a>';
+                    } else if (fileName.indexOf('pdf') != -1) {
+                        // PDF:点击预览
+                        actionHtml = '<a href="javascript:void(0)" class="attention-info" onclick="openPreviewWithRead(\'' + previewUrl + '\',1,\'' + fileId + '\')">' + name + '</a>';
+                    } else {
+                        // 其他文件:点击下载
+                        actionHtml = '<a href="javascript:void(0)" class="attention-info" onclick="openPreviewWithRead(\'' + previewUrl + '\',2,\'' + fileId + '\')">' + name + '</a>';
+                    }
+                } else if (item.type === 'link') {
+                    // 链接:点击时记录阅读量和点击量,然后跳转
+                    actionHtml = '<a href="javascript:void(0)" class="attention-info" style="color:#1E9FFF;" onclick="clickLinkWithRecord(this, \'' + item.url + '\', \'' + fileId + '\')">' + name + '</a>';
+                }
+                
+                html += '<tr>';
+                html += '<td style="text-align:center;">' + typeIcon + '</td>';
+                html += '<td style="word-break:break-all;text-align:center;padding-left:10px;">' + actionHtml + '</td>';
+                html += '</tr>';
+            }
+            
+            html += '</tbody></table></div>';
+            
             layer.open({
                 type: 1,
-                area: ['550px', '400px'],
+                area: ['600px', '400px'],
                 title: '附件列表',
-                content: '<div style="padding:15px;"><table id="attachmentListTable" class="table table-bordered" style="margin:0;"><thead><tr><th>文件名</th><th width="150">上传时间</th></tr></thead><tbody><tr><td colspan="2" style="text-align:center;">加载中...</td></tr></tbody></table></div>',
+                content: html,
                 btn: ['关闭'],
-                success: function(layero, index) {
-                    $.ajax({
-                        type: 'GET',
-                        url: '${ctx}/workKnowledgeBase/share/getAttachmentList',
-                        data: { fileId: fileId },
-                        success: function(result) {
-                            if (result.success && result.data && result.data.length > 0) {
-                                var tbody = '';
-                                for (var i = 0; i < result.data.length; i++) {
-                                    var att = result.data[i];
-                                    var previewUrl = att.temporaryUrl || att.url || '';
-                                    var fileName = att.name || '';
-                                    var createDate = att.createDate || '';
-                                    tbody += '<tr>';
-                                    tbody += '<td style="word-break:break-all;cursor:pointer;color:#1e9fff;text-align:center;" onclick="openPreviewAttachment(\'' + previewUrl.replace(/'/g, "\\'") + '\', \'' + fileName.replace(/'/g, "\\'") + '\')" title="点击查看详情">' + fileName + '</td>';
-                                    tbody += '<td style="text-align:center;">' + createDate + '</td>';
-                                    tbody += '</tr>';
-                                }
-                                $(layero).find('tbody').html(tbody);
-                            } else {
-                                $(layero).find('tbody').html('<tr><td colspan="2" style="text-align:center;">暂无附件</td></tr>');
-                            }
-                        },
-                        error: function() {
-                            $(layero).find('tbody').html('<tr><td colspan="2" style="text-align:center;color:red;">加载失败</td></tr>');
-                        }
-                    });
-                },
                 btn1: function(index) { layer.close(index); }
             });
         }
@@ -361,6 +401,39 @@
             }
         }
 
+        /** 点击链接时记录阅读量和点击量,然后跳转 */
+        function clickLinkWithRecord(element, linkUrl, fileId) {
+            // 记录阅读行为(审核通过后首次点击才触发)
+            $.ajax({
+                type: 'POST',
+                url: '${ctx}/workKnowledgeBase/readLike/recordRead',
+                data: { fileId: fileId },
+                success: function(result) {
+                    if (result.success && result.readCount != null) {
+                        updateCountInList(fileId, 'read-count', result.readCount);
+                    }
+                    // 递增点击量
+                    $.ajax({
+                        type: 'POST',
+                        url: '${ctx}/workKnowledgeBase/readLike/incrementClick',
+                        data: { fileId: fileId },
+                        success: function(clickResult) {
+                            if (clickResult.success && clickResult.clickCount != null) {
+                                updateCountInList(fileId, 'click-count', clickResult.clickCount);
+                            }
+                        }
+                    });
+                    // 打开链接
+                    window.open(linkUrl, '_blank');
+                },
+                error: function() {
+                    // 即使记录失败也打开链接
+                    window.open(linkUrl, '_blank');
+                }
+            });
+        }
+
+
         /** 审核详情弹窗(三按钮:通过/驳回/关闭) */
         function openAuditDetailDialog(id) {
             if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {
@@ -1097,32 +1170,60 @@
                 }
             }},
             {field: 'fileUrl', align: 'center', title: '文件地址', minWidth: 200, templet: function(d) {
-                // 多文件:逗号分隔展示文件名,点击弹出文件列表
-                if (d.attachmentCount > 1) {
+                var html = '';
+                // 解析统一内容项列表
+                var items = [];
+                if (d.contentItems && d.contentItems !== '[]') {
+                    try {
+                        items = JSON.parse(d.contentItems);
+                    } catch(e) {}
+                }
+                
+                if (items.length === 0) {
+                    // 无内容时显示空
+                    html = '<span style="color:#ccc;">-</span>';
+                } else if (items.length === 1) {
+                    // 只有1条时直接展示
+                    var item = items[0];
+                    if (item.type === 'file') {
+                        // 文件走原有逻辑
+                        if(item.uploadMode == '2'){
+                            if(item.name.toLowerCase().indexOf('jpg') != -1 || item.name.toLowerCase().indexOf('png') != -1 || item.name.toLowerCase().indexOf('gif') != -1 || item.name.toLowerCase().indexOf('bmp') != -1 || item.name.toLowerCase().indexOf('jpeg') != -1){
+                                html += "<img src="+item.temporaryUrl+" width='50' height='50' onclick=\"openDialogView('预览','${ctx}/sys/picturepreview/picturePreview?url=" + item.temporaryUrl +"','90%','90%')\" alt='"+item.name+"'>";
+                            }else if(item.name.toLowerCase().indexOf('pdf') != -1){
+                                html += "<a class=\"attention-info\" title=\"" + item.name + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + item.temporaryUrl +"',1,'" + d.id + "')\">" + item.name + "</a>";
+                            }else{
+                                html += "<a class=\"attention-info\" title=\"" + item.name + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + item.temporaryUrl +"',2,'" + d.id + "')\">" + item.name + "</a>";
+                            }
+                        }else{
+                            if(item.name.toLowerCase().indexOf('jpg') != -1 || item.name.toLowerCase().indexOf('png') != -1 || item.name.toLowerCase().indexOf('gif') != -1 || item.name.toLowerCase().indexOf('bmp') != -1 || item.name.toLowerCase().indexOf('jpeg') != -1){
+                                html += "<img src="+item.url+" width='50' height='50' onclick=\"openDialogView('预览','${ctx}/sys/picturepreview/picturePreview?url=" + item.url +"','90%','90%')\" alt='"+item.name+"'>";
+                            }else if(item.name.toLowerCase().indexOf('pdf') != -1){
+                                html += "<a class=\"attention-info\" title=\"" + item.name + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + item.url +"',1,'" + d.id + "')\">" + item.name + "</a>";
+                            }else{
+                                html += "<a class=\"attention-info\" title=\"" + item.name + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + item.url +"',2,'" + d.id + "')\">" + item.name + "</a>";
+                            }
+                        }
+                    } else if (item.type === 'link') {
+                        // 链接:点击时记录阅读量和点击量,然后跳转
+                        html += '<a href="javascript:void(0)" class="attention-info" style="color:#1e9fff;" onclick="clickLinkWithRecord(this, \'' + item.url + '\', \'' + d.id + '\')">' + (item.name || item.url) + '</a>';
+                    }
+                } else {
+                    // 多文件/链接:沿用原有多文件展示方式,用 ; 隔开
                     var displayName = d.allAttachmentNames || '';
+                    // 如果有链接,将链接描述也加入显示名称
+                    for (var i = 0; i < items.length; i++) {
+                        if (items[i].type === 'link') {
+                            if (displayName) displayName += '; ';
+                            displayName += items[i].name || items[i].url;
+                        }
+                    }
                     if (displayName.length > 40) {
                         displayName = displayName.substring(0, 40) + '...';
                     }
-                    return '<a href="javascript:void(0)" class="attention-info" onclick="showAttachmentList(\'' + d.id + '\')" title="' + (d.allAttachmentNames || '') + '">' + displayName + '</a>';
-                }
-                // 单文件:原有逻辑不变
-                if(2 == d.uploadMode){
-                    if(d.fileName.toLowerCase().indexOf('jpg') != -1 || d.fileName.toLowerCase().indexOf('png') != -1 || d.fileName.toLowerCase().indexOf('gif') != -1 || d.fileName.toLowerCase().indexOf('bmp') != -1 || d.fileName.toLowerCase().indexOf('jpeg') != -1){
-                        return "<img src="+d.temporaryUrl+" width='50' height='50' onclick=\"openDialogView('预览','${ctx}/sys/picturepreview/picturePreview?url=" + d.temporaryUrl +"','90%','90%')\" alt='"+d.fileName+"'>";
-                    }else if(d.fileName.toLowerCase().indexOf('pdf') != -1){
-                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + d.temporaryUrl +"',1,'" + d.id + "')\">" + d.fileName + "</a>";
-                    }else{
-                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + d.temporaryUrl +"',2,'" + d.id + "')\">" + d.fileName + "</a>";
-                    }
-                }else{
-                    if(d.fileName.toLowerCase().indexOf('jpg') != -1 || d.fileName.toLowerCase().indexOf('png') != -1 || d.fileName.toLowerCase().indexOf('gif') != -1 || d.fileName.toLowerCase().indexOf('bmp') != -1 || d.fileName.toLowerCase().indexOf('jpeg') != -1){
-                        return "<img src="+d.fileUrl+" width='50' height='50' onclick=\"openDialogView('预览','${ctx}/sys/picturepreview/picturePreview?url=" + d.fileUrl +"','90%','90%')\" alt='"+d.fileName+"'>";
-                    }else if(d.fileName.toLowerCase().indexOf('pdf') != -1){
-                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + d.fileUrl +"',1,'" + d.id + "')\">" + d.fileName + "</a>";
-                    }else{
-                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + d.fileUrl +"',2,'" + d.id + "')\">" + d.fileName + "</a>";
-                    }
+                    html = '<a href="javascript:void(0)" class="attention-info" onclick="showAttachmentList(\'' + d.id + '\')" title="' + (d.allAttachmentNames || '') + '">' + displayName + '</a>';
                 }
+                return html;
             }},
             {field: 'treeName', align: 'center', title: '文件来源', width: 120},
             <c:forEach items="${dynamicFields}" var="field">
@@ -1299,6 +1400,8 @@
                     ,"fileUrl": "<c:out value='${row.fileUrl}'/>"
                     ,"attachmentCount": ${row.attachmentCount != null ? row.attachmentCount : 0}
                     ,"allAttachmentNames": "<c:out value='${row.allAttachmentNames}'/>"
+                    ,"linkData": '${row.linkData != null ? row.linkData : "[]"}'
+                    ,"contentItems": '${row.contentItems != null ? row.contentItems : "[]"}'
                     ,"createTime": "${row.createTime}"
                     ,"createByName": "${row.createByName}"
                     ,"userid": "${fns:getUser().id}"