Jelajahi Sumber

知识库,指定人员有权限,可对内容进行撤销。并扣除相对应的分数

徐滕 1 Minggu lalu
induk
melakukan
150e6cb11e
15 mengubah file dengan 1166 tambahan dan 29 penghapusan
  1. 141 0
      docs/知识库违规处理功能说明.md
  2. 38 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBaseViolationRecordDao.java
  3. 22 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBasePointDetail.java
  4. 11 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBasePointRule.java
  5. 159 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBaseViolationRecord.java
  6. 17 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBaseReadLikeService.java
  7. 165 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBaseShareService.java
  8. 157 10
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBaseShareController.java
  9. 27 12
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBasePointDetailDao.xml
  10. 4 2
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBasePointRuleDao.xml
  11. 155 0
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBaseViolationRecordDao.xml
  12. 16 2
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBasePointRuleForm.jsp
  13. 4 0
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBasePointUserDetail.jsp
  14. 77 3
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareList.jsp
  15. 173 0
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseViolationHandle.jsp

+ 141 - 0
docs/知识库违规处理功能说明.md

@@ -0,0 +1,141 @@
+# 知识库违规处理功能 - 数据库与配置调整文档
+
+## 一、功能概述
+
+对已审核通过的知识库文档,具有特定权限的用户可以对其进行违规处理:
+- **作废**:逻辑删除文件(`del_flag='1'`),`audit_status` 设为 `10`,不可恢复
+- **积分扣除**:按倍数扣除文件创建人的积分,支持不扣分(倍数=0)、扣1倍、2倍等,允许扣成负数
+- 其他人因该文件的点赞、审核、阅读积分**不受影响**
+- 每次处理均记录处理原因、处理人、处理时间
+- 作废后文件不允许修改再上传,创建人如需重新提交请新增一条记录
+
+---
+
+## 二、数据库变更
+
+### 2.1 新建违规处理记录表
+
+```sql
+CREATE TABLE work_knowledge_base_violation_record (
+    id                  VARCHAR(64)     NOT NULL    COMMENT '主键ID',
+    file_id             VARCHAR(64)     NOT NULL    COMMENT '关联文件ID(work_knowledge_base_share_info.id)',
+    target_user_id      VARCHAR(64)     NOT NULL    COMMENT '被处罚用户ID(文件创建人)',
+    handle_type         VARCHAR(20)     NOT NULL    COMMENT '处理方式:invalid=作废',
+    original_points     INT             DEFAULT 0   COMMENT '原获得积分数',
+    point_multiplier    DECIMAL(3,1)    DEFAULT 0   COMMENT '扣积分倍数(0=不扣分,1=1倍,2=2倍)',
+    deducted_points     INT             DEFAULT 0   COMMENT '实际扣除积分数(负数)',
+    handle_reason       TEXT                            COMMENT '处理原因',
+    handle_user_id      VARCHAR(64)                     COMMENT '处理人ID',
+    handle_time         DATETIME                        COMMENT '处理时间',
+    original_audit_status VARCHAR(20)                   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),
+    INDEX idx_target_user_id (target_user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库违规处理记录表';
+```
+
+### 2.2 积分明细表新增字段
+
+```sql
+ALTER TABLE work_knowledge_base_point_detail
+    ADD COLUMN is_deduction  CHAR(1)      DEFAULT '0'  COMMENT '是否为违规扣分记录(0否 1是)' AFTER manual_score_time,
+    ADD COLUMN violation_id  VARCHAR(64)  DEFAULT NULL  COMMENT '关联违规处理记录ID' AFTER is_deduction;
+```
+
+### 2.3 积分规则表新增字段
+
+```sql
+ALTER TABLE work_knowledge_base_point_rule
+    ADD COLUMN is_deduction  CHAR(1)      DEFAULT '0'  COMMENT '是否为扣分项标识(0否 1是)' AFTER point_max;
+```
+
+> **说明**:`is_deduction` 字段用于在积分规则表中标记该规则是否为扣分类型,方便在积分详情信息中区分展示。
+
+---
+
+## 三、权限配置
+
+### 3.1 新增菜单权限
+
+需要在系统菜单管理中添加权限标识:
+
+| 字段 | 值 |
+|------|------|
+| 权限标识 | `workKnowledgeBase:share:violationHandle` |
+| 菜单名称 | 违规处理 |
+| 权限类型 | 按钮 |
+| 父级菜单 | 知识库-文件信息管理 |
+
+### 3.2 角色分配
+
+将 `workKnowledgeBase:share:violationHandle` 权限分配给需要拥有违规处理能力的角色(如质量管理员、管理员等)。
+
+**操作路径**:系统管理 → 角色管理 → 编辑角色 → 勾选"违规处理"权限
+
+---
+
+## 四、新增/修改文件清单
+
+### 4.1 新增文件
+
+| 文件路径 | 说明 |
+|---------|------|
+| `src/main/java/.../entity/WorkKnowledgeBaseViolationRecord.java` | 违规处理记录实体类 |
+| `src/main/java/.../dao/WorkKnowledgeBaseViolationRecordDao.java` | 违规处理DAO接口 |
+| `src/main/resources/mappings/.../WorkKnowledgeBaseViolationRecordDao.xml` | MyBatis映射XML |
+| `src/main/webapp/.../workKnowledgeBaseViolationHandle.jsp` | 违规处理表单JSP页面 |
+
+### 4.2 修改文件
+
+| 文件路径 | 修改内容 |
+|---------|---------|
+| `WorkKnowledgeBaseShareService.java` | 新增 `handleViolation()`、`getViolationRecords()`、`hasBeenHandled()`、`getPointDetailList()` 方法;新增 `AUDIT_STATUS_INVALID` 和 `POINT_CHANGE_DEDUCTION` 常量 |
+| `WorkKnowledgeBaseShareController.java` | 新增 `violationHandleForm()`、`violationHandleSave()`、`getViolationRecords()` 接口 |
+| `workKnowledgeBaseShareList.jsp` | 添加违规处理按钮和 `openViolationHandleDialog()` JS函数;添加权限标识 `canViolationHandle` |
+| `WorkKnowledgeBasePointDetail.java` | 新增 `isDeduction`、`violationId` 字段 |
+| `WorkKnowledgeBasePointRule.java` | 新增 `isDeduction` 字段 |
+| `WorkKnowledgeBasePointDetailDao.xml` | columns SQL和insert语句新增 `is_deduction`、`violation_id` 字段 |
+| `WorkKnowledgeBasePointRuleDao.xml` | columns SQL、insert和update语句新增 `is_deduction` 字段 |
+
+---
+
+## 五、业务规则说明
+
+1. **只有审核通过(`audit_status=5`)的文件才能被违规处理**
+2. **作废(`invalid`)**:`audit_status` 设为 `10`,文件逻辑删除,不可恢复。作废后不允许修改再上传,创建人如需重新提交请新增一条记录
+3. **积分扣除**:
+   - 倍数=0:不扣分
+   - 倍数=1:扣除该文件给创建人带来的全部积分
+   - 倍数=2:扣除2倍积分(允许扣成负数)
+   - 扣分记录写入 `work_knowledge_base_point_detail`,`change_type=5`,`is_deduction='1'`
+4. **其他人积分不受影响**:点赞、审核、阅读积分不做处理
+5. **每次处理都会记录**:处理原因、处理人、处理时间存入 `work_knowledge_base_violation_record`
+
+---
+
+## 六、其他修复
+
+### 6.1 主审专家编辑时不回显问题修复
+
+**问题**:点击修改时,之前选择的主审专家没有展示出来。
+
+**原因**:`form` 方法中 `shareInfo` 由Spring MVC从URL参数绑定(URL只有 `id`/`treeNodeId`/`rootId`),`expertReviewerIds` 不在URL中,始终为null,导致姓名反查逻辑永远不执行。
+
+**修复**:编辑时先通过 `shareService.get(shareInfo.getId())` 从DB加载完整实体获取 `expertReviewerIds`,再反查姓名设置到 `shareInfo.setExpertReviewerNames()`。
+
+---
+
+## 七、执行顺序
+
+1. 执行 **2.1** 建表SQL
+2. 执行 **2.2** 积分明细表ALTER SQL
+3. 执行 **2.3** 积分规则表ALTER SQL
+4. 在系统菜单中添加权限 `workKnowledgeBase:share:violationHandle`
+5. 将权限分配给对应角色
+6. 部署代码

+ 38 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBaseViolationRecordDao.java

@@ -0,0 +1,38 @@
+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.WorkKnowledgeBaseViolationRecord;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 知识库违规处理记录 DAO
+ * @author: 徐滕
+ * @version: 2026-07-06
+ */
+@MyBatisDao
+public interface WorkKnowledgeBaseViolationRecordDao extends CrudDao<WorkKnowledgeBaseViolationRecord> {
+
+    /**
+     * 根据文件ID查询违规处理记录列表
+     * @param fileId 文件ID
+     * @return 违规处理记录列表
+     */
+    List<WorkKnowledgeBaseViolationRecord> findByFileId(@Param("fileId") String fileId);
+
+    /**
+     * 根据文件ID统计处理记录数
+     * @param fileId 文件ID
+     * @return 记录数
+     */
+    int countByFileId(@Param("fileId") String fileId);
+
+    /**
+     * 根据文件ID查询最新的处理记录
+     * @param fileId 文件ID
+     * @return 最新处理记录
+     */
+    WorkKnowledgeBaseViolationRecord findLatestByFileId(@Param("fileId") String fileId);
+}

+ 22 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBasePointDetail.java

@@ -40,6 +40,12 @@ public class WorkKnowledgeBasePointDetail extends DataEntity<WorkKnowledgeBasePo
     /** 赋分时间(仅手动赋分时填写) */
     /** 赋分时间(仅手动赋分时填写) */
     private java.util.Date manualScoreTime;
     private java.util.Date manualScoreTime;
 
 
+    /** 是否为违规扣分记录(0否 1是) */
+    private String isDeduction;
+
+    /** 关联违规处理记录ID(仅扣分记录时填写) */
+    private String violationId;
+
     public String getUserId() {
     public String getUserId() {
         return userId;
         return userId;
     }
     }
@@ -119,4 +125,20 @@ public class WorkKnowledgeBasePointDetail extends DataEntity<WorkKnowledgeBasePo
     public void setManualScoreTime(java.util.Date manualScoreTime) {
     public void setManualScoreTime(java.util.Date manualScoreTime) {
         this.manualScoreTime = manualScoreTime;
         this.manualScoreTime = manualScoreTime;
     }
     }
+
+    public String getIsDeduction() {
+        return isDeduction;
+    }
+
+    public void setIsDeduction(String isDeduction) {
+        this.isDeduction = isDeduction;
+    }
+
+    public String getViolationId() {
+        return violationId;
+    }
+
+    public void setViolationId(String violationId) {
+        this.violationId = violationId;
+    }
 }
 }

+ 11 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBasePointRule.java

@@ -33,6 +33,9 @@ public class WorkKnowledgeBasePointRule extends DataEntity<WorkKnowledgeBasePoin
     /** 区间积分上限(point_mode=2生效) */
     /** 区间积分上限(point_mode=2生效) */
     private String pointMax;
     private String pointMax;
 
 
+    /** 是否为扣分项标识(0否 1是,用于标记该规则为违规扣分类型) */
+    private String isDeduction;
+
     /** 归属分类名称(关联查询,非数据库字段) */
     /** 归属分类名称(关联查询,非数据库字段) */
     private String categoryName;
     private String categoryName;
 
 
@@ -100,6 +103,14 @@ public class WorkKnowledgeBasePointRule extends DataEntity<WorkKnowledgeBasePoin
         this.pointMax = pointMax;
         this.pointMax = pointMax;
     }
     }
 
 
+    public String getIsDeduction() {
+        return isDeduction;
+    }
+
+    public void setIsDeduction(String isDeduction) {
+        this.isDeduction = isDeduction;
+    }
+
     public String getCategoryName() {
     public String getCategoryName() {
         return categoryName;
         return categoryName;
     }
     }

+ 159 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBaseViolationRecord.java

@@ -0,0 +1,159 @@
+package com.jeeplus.modules.WorkKnowledgeBase.entity;
+
+import com.jeeplus.common.persistence.DataEntity;
+
+import java.util.Date;
+
+/**
+ * 知识库违规处理记录实体
+ * 对应表 work_knowledge_base_violation_record
+ * @author: 徐滕
+ * @version: 2026-07-06
+ */
+public class WorkKnowledgeBaseViolationRecord extends DataEntity<WorkKnowledgeBaseViolationRecord> {
+
+    /** 关联文件ID(work_knowledge_base_share_info.id) */
+    private String fileId;
+
+    /** 被处罚用户ID(文件创建人/提交人) */
+    private String targetUserId;
+
+    /** 处理方式: invalid=作废, return=退回 */
+    private String handleType;
+
+    /** 原获得积分数 */
+    private Integer originalPoints;
+
+    /** 扣积分倍数(0=不扣分, 1=1倍, 2=2倍...) */
+    private java.math.BigDecimal pointMultiplier;
+
+    /** 实际扣除积分数(负数) */
+    private Integer deductedPoints;
+
+    /** 处理原因 */
+    private String handleReason;
+
+    /** 处理人ID */
+    private String handleUserId;
+
+    /** 处理时间 */
+    private Date handleTime;
+
+    /** 文件原始审核状态(处理前的audit_status,用于退回时恢复) */
+    private String originalAuditStatus;
+
+    // ===== 非数据库字段,用于展示 =====
+
+    /** 被处罚用户姓名 */
+    private String targetUserName;
+
+    /** 处理人姓名 */
+    private String handleUserName;
+
+    /** 文件名称 */
+    private String fileName;
+
+    public String getFileId() {
+        return fileId;
+    }
+
+    public void setFileId(String fileId) {
+        this.fileId = fileId;
+    }
+
+    public String getTargetUserId() {
+        return targetUserId;
+    }
+
+    public void setTargetUserId(String targetUserId) {
+        this.targetUserId = targetUserId;
+    }
+
+    public String getHandleType() {
+        return handleType;
+    }
+
+    public void setHandleType(String handleType) {
+        this.handleType = handleType;
+    }
+
+    public Integer getOriginalPoints() {
+        return originalPoints;
+    }
+
+    public void setOriginalPoints(Integer originalPoints) {
+        this.originalPoints = originalPoints;
+    }
+
+    public java.math.BigDecimal getPointMultiplier() {
+        return pointMultiplier;
+    }
+
+    public void setPointMultiplier(java.math.BigDecimal pointMultiplier) {
+        this.pointMultiplier = pointMultiplier;
+    }
+
+    public Integer getDeductedPoints() {
+        return deductedPoints;
+    }
+
+    public void setDeductedPoints(Integer deductedPoints) {
+        this.deductedPoints = deductedPoints;
+    }
+
+    public String getHandleReason() {
+        return handleReason;
+    }
+
+    public void setHandleReason(String handleReason) {
+        this.handleReason = handleReason;
+    }
+
+    public String getHandleUserId() {
+        return handleUserId;
+    }
+
+    public void setHandleUserId(String handleUserId) {
+        this.handleUserId = handleUserId;
+    }
+
+    public Date getHandleTime() {
+        return handleTime;
+    }
+
+    public void setHandleTime(Date handleTime) {
+        this.handleTime = handleTime;
+    }
+
+    public String getOriginalAuditStatus() {
+        return originalAuditStatus;
+    }
+
+    public void setOriginalAuditStatus(String originalAuditStatus) {
+        this.originalAuditStatus = originalAuditStatus;
+    }
+
+    public String getTargetUserName() {
+        return targetUserName;
+    }
+
+    public void setTargetUserName(String targetUserName) {
+        this.targetUserName = targetUserName;
+    }
+
+    public String getHandleUserName() {
+        return handleUserName;
+    }
+
+    public void setHandleUserName(String handleUserName) {
+        this.handleUserName = handleUserName;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+}

+ 17 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBaseReadLikeService.java

@@ -117,6 +117,12 @@ public class WorkKnowledgeBaseReadLikeService extends CrudService<WorkKnowledgeB
             return false;
             return false;
         }
         }
 
 
+        // 只有审核通过(5)的文件才能点赞,作废(10)等状态不允许点赞
+        WorkKnowledgeBaseShareInfo fileStatus = shareInfoDao.get(fileId);
+        if (fileStatus == null || !"5".equals(fileStatus.getAuditStatus())) {
+            return false;
+        }
+
         // 查询是否有点赞记录
         // 查询是否有点赞记录
         WorkKnowledgeBaseLikeRecord existingRecord = likeRecordDao.findByFileIdAndUserId(fileId, userId);
         WorkKnowledgeBaseLikeRecord existingRecord = likeRecordDao.findByFileIdAndUserId(fileId, userId);
 
 
@@ -163,6 +169,12 @@ public class WorkKnowledgeBaseReadLikeService extends CrudService<WorkKnowledgeB
             return;
             return;
         }
         }
 
 
+        // 作废(10)等状态的文件不允许操作点赞
+        WorkKnowledgeBaseShareInfo fileStatus = shareInfoDao.get(fileId);
+        if (fileStatus == null || !"5".equals(fileStatus.getAuditStatus())) {
+            return;
+        }
+
         WorkKnowledgeBaseLikeRecord record = likeRecordDao.findByFileIdAndUserId(fileId, userId);
         WorkKnowledgeBaseLikeRecord record = likeRecordDao.findByFileIdAndUserId(fileId, userId);
         if (record != null && record.getStatus() == 1) {
         if (record != null && record.getStatus() == 1) {
             // 更新点赞状态为已取消
             // 更新点赞状态为已取消
@@ -313,6 +325,11 @@ public class WorkKnowledgeBaseReadLikeService extends CrudService<WorkKnowledgeB
                 return;
                 return;
             }
             }
 
 
+            // 只有审核通过(audit_status='5')的文件才给点赞积分,作废(10)等状态不给积分
+            if (!"5".equals(shareInfo.getAuditStatus())) {
+                return;
+            }
+
             // 判断点赞人是否是记录所有者(与前端isCreator逻辑一致)
             // 判断点赞人是否是记录所有者(与前端isCreator逻辑一致)
             // 如果submitAuditUserId有值,则判定是否是最后修改人
             // 如果submitAuditUserId有值,则判定是否是最后修改人
             // 如果submitAuditUserId为空,则判定是否是创建人
             // 如果submitAuditUserId为空,则判定是否是创建人

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

@@ -48,6 +48,9 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
     public static final String AUDIT_STATUS_REJECTED = "4";
     public static final String AUDIT_STATUS_REJECTED = "4";
     /** 审核状态常量:审核通过 */
     /** 审核状态常量:审核通过 */
     public static final String AUDIT_STATUS_PASSED = "5";
     public static final String AUDIT_STATUS_PASSED = "5";
+
+    /** 审核状态常量:作废 */
+    public static final String AUDIT_STATUS_INVALID = "10";
     
     
     /** 问答状态常量:待回答(问答专用) */
     /** 问答状态常量:待回答(问答专用) */
     public static final String QA_STATUS_WAITING = "6";
     public static final String QA_STATUS_WAITING = "6";
@@ -69,6 +72,8 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
     private static final Integer POINT_CHANGE_AUDIT = 2;
     private static final Integer POINT_CHANGE_AUDIT = 2;
     /** 积分明细变动类型:手动赋分加分 */
     /** 积分明细变动类型:手动赋分加分 */
     private static final Integer POINT_CHANGE_MANUAL = 3;
     private static final Integer POINT_CHANGE_MANUAL = 3;
+    /** 积分明细变动类型:违规扣分 */
+    private static final Integer POINT_CHANGE_DEDUCTION = 5;
     /** 问答积分规则类型:提问积分 */
     /** 问答积分规则类型:提问积分 */
     private static final String QA_RULE_TYPE_QUESTION = "1";
     private static final String QA_RULE_TYPE_QUESTION = "1";
     /** 问答积分规则类型:回答积分 */
     /** 问答积分规则类型:回答积分 */
@@ -116,6 +121,9 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
     @Autowired
     @Autowired
     private WorkKnowledgeBaseUserExchangeFlagDao exchangeFlagDao;
     private WorkKnowledgeBaseUserExchangeFlagDao exchangeFlagDao;
 
 
+    @Autowired
+    private WorkKnowledgeBaseViolationRecordDao violationRecordDao;
+
     /**
     /**
      * 根据文件id加载附件列表(编辑表单回显用)
      * 根据文件id加载附件列表(编辑表单回显用)
      */
      */
@@ -2443,4 +2451,161 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
         insertPointDetailForManualScore(targetUserId, fileId, POINT_CHANGE_MANUAL, categoryId,
         insertPointDetailForManualScore(targetUserId, fileId, POINT_CHANGE_MANUAL, categoryId,
                 scoreRuleId, score, scoreUserId, targetUserId, manualScoreTime);
                 scoreRuleId, score, scoreUserId, targetUserId, manualScoreTime);
     }
     }
+
+    /**
+     * 违规处理:作废/退回 + 积分扣除
+     * @param fileId 文件ID
+     * @param handleType 处理方式: invalid=作废, return=退回
+     * @param pointMultiplier 扣积分倍数(0=不扣分, 1=1倍, 2=2倍...)
+     * @param handleReason 处理原因
+     * @param handleUserId 处理人ID
+     */
+    @Transactional(readOnly = false)
+    public void handleViolation(String fileId, String handleType, java.math.BigDecimal pointMultiplier,
+                                String handleReason, String handleUserId) {
+        WorkKnowledgeBaseShareInfo entity = dao.get(fileId);
+        if (entity == null) {
+            throw new RuntimeException("文件记录不存在");
+        }
+        // 只有审核通过的文件才能被处理
+        if (!AUDIT_STATUS_PASSED.equals(entity.getAuditStatus())) {
+            throw new RuntimeException("只有审核通过的文件才能进行违规处理");
+        }
+        // 已作废的文件不能再处理
+        if (AUDIT_STATUS_INVALID.equals(entity.getAuditStatus())) {
+            throw new RuntimeException("该文件已作废,无法再次处理");
+        }
+
+        // 确定被处罚人(文件提交人/创建人)
+        String targetUserId = entity.getSubmitAuditUserId();
+        if (StringUtils.isBlank(targetUserId) && entity.getCreateBy() != null) {
+            targetUserId = entity.getCreateBy().getId();
+        }
+        if (StringUtils.isBlank(targetUserId)) {
+            throw new RuntimeException("无法确定文件创建人");
+        }
+
+        // 计算该文件给目标用户带来的总积分
+        int totalPoints = calculateFilePointsForUser(fileId, targetUserId);
+
+        // 计算扣除积分
+        int deductedPoints = 0;
+        if (pointMultiplier != null && pointMultiplier.compareTo(java.math.BigDecimal.ZERO) > 0) {
+            deductedPoints = (int) Math.round(totalPoints * pointMultiplier.doubleValue());
+        }
+
+        // 保存违规处理记录
+        WorkKnowledgeBaseViolationRecord record = new WorkKnowledgeBaseViolationRecord();
+        record.setId(IdGen.uuid());
+        record.setFileId(fileId);
+        record.setTargetUserId(targetUserId);
+        record.setHandleType(handleType);
+        record.setOriginalPoints(totalPoints);
+        record.setPointMultiplier(pointMultiplier);
+        record.setDeductedPoints(deductedPoints > 0 ? -deductedPoints : 0);
+        record.setHandleReason(handleReason);
+        record.setHandleUserId(handleUserId);
+        record.setHandleTime(new Date());
+        record.setOriginalAuditStatus(entity.getAuditStatus());
+        record.preInsert();
+        violationRecordDao.insert(record);
+
+        // 插入积分扣除明细记录
+        if (deductedPoints > 0) {
+            // 查询"扣分标准"分类ID
+            String deductionCategoryId = null;
+            String deductionRuleId = null;
+            try {
+                List<com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory> deductionCategories =
+                    findCategoriesByName("扣分标准");
+                if (deductionCategories != null && !deductionCategories.isEmpty()) {
+                    deductionCategoryId = deductionCategories.get(0).getId();
+                    // 查询"违规扣分"规则(rule_type=5)
+                    WorkKnowledgeBasePointRule deductionRule = pointRuleDao.findByCategoryIdAndRuleType(deductionCategoryId, "5");
+                    if (deductionRule != null) {
+                        deductionRuleId = deductionRule.getId();
+                    }
+                }
+            } catch (Exception e) {
+                // 查询失败不影响主流程,category_id和rule_id保持null
+            }
+
+            WorkKnowledgeBasePointDetail detail = new WorkKnowledgeBasePointDetail();
+            detail.setId(IdGen.uuid());
+            detail.setUserId(targetUserId);
+            detail.setShareId(fileId);
+            detail.setChangeType(POINT_CHANGE_DEDUCTION);
+            detail.setCategoryId(deductionCategoryId); // 扣分标准分类ID
+            detail.setRuleId(deductionRuleId); // 违规扣分规则ID
+            detail.setPoint(-deductedPoints);
+            detail.setIsDeduction("1");
+            detail.setViolationId(record.getId());
+            detail.setIsManualScore("0");
+            detail.preInsert();
+            pointDetailDao.insert(detail);
+        }
+
+        // 作废:仅设置状态为10,不做逻辑删除(保留记录可见性)
+        entity.setAuditStatus(AUDIT_STATUS_INVALID);
+        entity.preUpdate();
+        dao.updateAuditStatus(entity);
+    }
+
+    /**
+     * 计算某个文件给某个用户带来的创建积分(change_type=1)
+     */
+    private int calculateFilePointsForUser(String fileId, String userId) {
+        int total = 0;
+        // 只查询创建积分(change_type=1),不包含审核、阅读、点赞等其他积分
+        WorkKnowledgeBasePointDetail query = new WorkKnowledgeBasePointDetail();
+        query.setUserId(userId);
+        query.setShareId(fileId);
+        query.setChangeType(POINT_CHANGE_CREATE);
+        List<WorkKnowledgeBasePointDetail> details = pointDetailDao.findList(query);
+        if (details != null) {
+            for (WorkKnowledgeBasePointDetail d : details) {
+                if (d.getPoint() != null && d.getPoint() > 0) {
+                    total += d.getPoint();
+                }
+            }
+        }
+        return total;
+    }
+
+    /**
+     * 查询文件的违规处理记录列表
+     */
+    public List<WorkKnowledgeBaseViolationRecord> getViolationRecords(String fileId) {
+        if (StringUtils.isBlank(fileId)) {
+            return new ArrayList<>();
+        }
+        return violationRecordDao.findByFileId(fileId);
+    }
+
+    /**
+     * 查询文件最新的违规处理记录
+     */
+    public WorkKnowledgeBaseViolationRecord getLatestViolationRecord(String fileId) {
+        if (StringUtils.isBlank(fileId)) {
+            return null;
+        }
+        return violationRecordDao.findLatestByFileId(fileId);
+    }
+
+    /**
+     * 检查文件是否已被违规处理
+     */
+    public boolean hasBeenHandled(String fileId) {
+        if (StringUtils.isBlank(fileId)) {
+            return false;
+        }
+        return violationRecordDao.countByFileId(fileId) > 0;
+    }
+
+    /**
+     * 查询积分明细列表(用于Controller计算原始积分)
+     */
+    public List<WorkKnowledgeBasePointDetail> getPointDetailList(WorkKnowledgeBasePointDetail query) {
+        return pointDetailDao.findList(query);
+    }
 }
 }

+ 157 - 10
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBaseShareController.java

@@ -409,20 +409,25 @@ public class WorkKnowledgeBaseShareController extends BaseController {
         }
         }
 
 
         // 编辑时回显主审专家姓名(expertReviewerNames 为非数据库字段,需根据ID反查)
         // 编辑时回显主审专家姓名(expertReviewerNames 为非数据库字段,需根据ID反查)
-        if (StringUtils.isNotBlank(shareInfo.getExpertReviewerIds())) {
-            StringBuilder namesBuilder = new StringBuilder();
-            for (String eid : shareInfo.getExpertReviewerIds().split(",")) {
-                if (StringUtils.isNotBlank(eid)) {
-                    com.jeeplus.modules.sys.entity.User expertUser = UserUtils.get(eid.trim());
-                    if (expertUser != null && StringUtils.isNotBlank(expertUser.getName())) {
-                        if (namesBuilder.length() > 0) {
-                            namesBuilder.append(",");
+        // 注意:shareInfo由URL参数绑定,expertReviewerIds不在URL中,需从DB加载完整实体获取
+        if (StringUtils.isNotBlank(shareInfo.getId())) {
+            WorkKnowledgeBaseShareInfo dbEntity = shareService.get(shareInfo.getId());
+            if (dbEntity != null && StringUtils.isNotBlank(dbEntity.getExpertReviewerIds())) {
+                shareInfo.setExpertReviewerIds(dbEntity.getExpertReviewerIds());
+                StringBuilder namesBuilder = new StringBuilder();
+                for (String eid : dbEntity.getExpertReviewerIds().split(",")) {
+                    if (StringUtils.isNotBlank(eid)) {
+                        com.jeeplus.modules.sys.entity.User expertUser = UserUtils.get(eid.trim());
+                        if (expertUser != null && StringUtils.isNotBlank(expertUser.getName())) {
+                            if (namesBuilder.length() > 0) {
+                                namesBuilder.append(",");
+                            }
+                            namesBuilder.append(expertUser.getName());
                         }
                         }
-                        namesBuilder.append(expertUser.getName());
                     }
                     }
                 }
                 }
+                shareInfo.setExpertReviewerNames(namesBuilder.toString());
             }
             }
-            shareInfo.setExpertReviewerNames(namesBuilder.toString());
         }
         }
 
 
         // 判断是否为问答答疑分类
         // 判断是否为问答答疑分类
@@ -1449,4 +1454,146 @@ public class WorkKnowledgeBaseShareController extends BaseController {
         }
         }
         return userList;
         return userList;
     }
     }
+
+    /**
+     * 违规处理页面
+     */
+    @RequiresPermissions("workKnowledgeBase:share:violationHandle")
+    @RequestMapping(value = "violationHandleForm")
+    public String violationHandleForm(@RequestParam String id, Model model) {
+        WorkKnowledgeBaseShareInfo entity = shareService.get(id);
+        if (entity == null) {
+            addMessage(model, "记录不存在!");
+            return "error";
+        }
+        if (!"5".equals(entity.getAuditStatus())) {
+            addMessage(model, "只有审核通过的文件才能进行违规处理");
+            return "error";
+        }
+
+        // 查询该文件的违规处理历史
+        List<com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord> historyRecords =
+                shareService.getViolationRecords(id);
+
+        // 确定文件创建人/提交人信息
+        String targetUserId = entity.getSubmitAuditUserId();
+        if (StringUtils.isBlank(targetUserId) && entity.getCreateBy() != null) {
+            targetUserId = entity.getCreateBy().getId();
+        }
+        String targetUserName = "";
+        if (StringUtils.isNotBlank(targetUserId)) {
+            com.jeeplus.modules.sys.entity.User targetUser = UserUtils.get(targetUserId);
+            if (targetUser != null) {
+                targetUserName = targetUser.getName();
+            }
+        }
+
+        model.addAttribute("entity", entity);
+        model.addAttribute("targetUserId", targetUserId);
+        model.addAttribute("targetUserName", targetUserName);
+        model.addAttribute("historyRecords", historyRecords);
+
+        // 计算该文件给目标用户带来的创建积分(change_type=1),用于前端扣分预览
+        int originalPoints = 0;
+        if (StringUtils.isNotBlank(targetUserId)) {
+            com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointDetail query =
+                    new com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointDetail();
+            query.setUserId(targetUserId);
+            query.setShareId(id);
+            query.setChangeType(1); // 只统计创建积分
+            // 使用Service的pointDetailDao查询
+            java.util.List<com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointDetail> details =
+                    shareService.getPointDetailList(query);
+            if (details != null) {
+                for (com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointDetail d : details) {
+                    if (d.getPoint() != null && d.getPoint() > 0) {
+                        originalPoints += d.getPoint();
+                    }
+                }
+            }
+        }
+        model.addAttribute("originalPoints", originalPoints);
+
+        return "modules/WorkKnowledgeBase/workKnowledgeBaseViolationHandle";
+    }
+
+    /**
+     * 违规处理提交(Ajax)
+     */
+    @RequiresPermissions("workKnowledgeBase:share:violationHandle")
+    @RequestMapping(value = "violationHandleSave")
+    @ResponseBody
+    public Map<String, Object> violationHandleSave(@RequestParam String id,
+                                                    @RequestParam String handleType,
+                                                    @RequestParam java.math.BigDecimal pointMultiplier,
+                                                    @RequestParam(required = false) String handleReason) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            String currentUserId = UserUtils.getUser().getId();
+            shareService.handleViolation(id, handleType, pointMultiplier, handleReason, currentUserId);
+            result.put("success", true);
+            result.put("message", "违规处理成功");
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 查询文件最新的作废原因(公开接口,所有登录用户可访问)
+     */
+    @RequestMapping(value = "getInvalidationReason")
+    @ResponseBody
+    public Map<String, Object> getInvalidationReason(@RequestParam String fileId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord record =
+                    shareService.getLatestViolationRecord(fileId);
+            if (record != null) {
+                result.put("handleReason", record.getHandleReason() != null ? record.getHandleReason() : "");
+                result.put("handleUserName", record.getHandleUserName() != null ? record.getHandleUserName() : "");
+                result.put("handleTime", record.getHandleTime() != null ?
+                        DateUtils.formatDateTime(record.getHandleTime()) : "");
+                result.put("deductedPoints", record.getDeductedPoints());
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+        return result;
+    }
+
+    /**
+     * 查询文件的违规处理记录(Ajax)
+     */
+    @RequiresPermissions("workKnowledgeBase:share:violationHandle")
+    @RequestMapping(value = "getViolationRecords")
+    @ResponseBody
+    public List<Map<String, Object>> getViolationRecords(@RequestParam String fileId) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        try {
+            List<com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord> records =
+                    shareService.getViolationRecords(fileId);
+            if (records != null) {
+                for (com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord r : records) {
+                    Map<String, Object> item = new HashMap<>();
+                    item.put("id", r.getId());
+                    item.put("handleType", r.getHandleType());
+                    item.put("handleTypeName", "invalid".equals(r.getHandleType()) ? "作废" : "退回");
+                    item.put("originalPoints", r.getOriginalPoints());
+                    item.put("pointMultiplier", r.getPointMultiplier());
+                    item.put("deductedPoints", r.getDeductedPoints());
+                    item.put("handleReason", r.getHandleReason());
+                    item.put("handleUserName", r.getHandleUserName());
+                    item.put("handleTime", r.getHandleTime() != null ?
+                            DateUtils.formatDateTime(r.getHandleTime()) : "");
+                    item.put("targetUserName", r.getTargetUserName());
+                    resultList.add(item);
+                }
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+        return resultList;
+    }
 }
 }

+ 27 - 12
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBasePointDetailDao.xml

@@ -14,6 +14,8 @@
         a.manual_score_user_id AS "manualScoreUserId",
         a.manual_score_user_id AS "manualScoreUserId",
         a.manual_score_target_user_id AS "manualScoreTargetUserId",
         a.manual_score_target_user_id AS "manualScoreTargetUserId",
         a.manual_score_time AS "manualScoreTime",
         a.manual_score_time AS "manualScoreTime",
+        a.is_deduction    AS "isDeduction",
+        a.violation_id    AS "violationId",
         a.create_by     AS "createBy.id",
         a.create_by     AS "createBy.id",
         a.create_date   AS "createDate",
         a.create_date   AS "createDate",
         a.update_by     AS "updateBy.id",
         a.update_by     AS "updateBy.id",
@@ -31,7 +33,18 @@
     <select id="findList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointDetail">
     <select id="findList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointDetail">
         SELECT <include refid="pointDetailColumns"/>
         SELECT <include refid="pointDetailColumns"/>
         FROM work_knowledge_base_point_detail a
         FROM work_knowledge_base_point_detail a
-        WHERE a.del_flag = #{DEL_FLAG_NORMAL}
+        <where>
+            a.del_flag = #{DEL_FLAG_NORMAL}
+            <if test="userId != null and userId != ''">
+                AND a.user_id = #{userId}
+            </if>
+            <if test="shareId != null and shareId != ''">
+                AND a.share_id = #{shareId}
+            </if>
+            <if test="changeType != null">
+                AND a.change_type = #{changeType}
+            </if>
+        </where>
         ORDER BY a.create_date DESC
         ORDER BY a.create_date DESC
     </select>
     </select>
 
 
@@ -66,10 +79,12 @@
         INSERT INTO work_knowledge_base_point_detail (
         INSERT INTO work_knowledge_base_point_detail (
             id, user_id, share_id, change_type, category_id, rule_id, point,
             id, user_id, share_id, change_type, category_id, rule_id, point,
             is_manual_score, manual_score_user_id, manual_score_target_user_id, manual_score_time,
             is_manual_score, manual_score_user_id, manual_score_target_user_id, manual_score_time,
+            is_deduction, violation_id,
             create_by, create_date, update_by, update_date, remarks, del_flag
             create_by, create_date, update_by, update_date, remarks, del_flag
         ) VALUES (
         ) VALUES (
             #{id}, #{userId}, #{shareId}, #{changeType}, #{categoryId}, #{ruleId}, #{point},
             #{id}, #{userId}, #{shareId}, #{changeType}, #{categoryId}, #{ruleId}, #{point},
             #{isManualScore}, #{manualScoreUserId}, #{manualScoreTargetUserId}, #{manualScoreTime},
             #{isManualScore}, #{manualScoreUserId}, #{manualScoreTargetUserId}, #{manualScoreTime},
+            #{isDeduction}, #{violationId},
             #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{remarks}, #{delFlag}
             #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{remarks}, #{delFlag}
         )
         )
     </insert>
     </insert>
@@ -116,9 +131,9 @@
         LEFT JOIN sys_office o ON o.id = u.office_id
         LEFT JOIN sys_office o ON o.id = u.office_id
         <where>
         <where>
             u.del_flag = '0'
             u.del_flag = '0'
-            <!-- 按积分分类节点及其所有子节点筛选 -->
+            <!-- 按积分分类节点及其所有子节点筛选,始终包含扣分标准(id=10)的记录 -->
             <if test="columnId != null and columnId != ''">
             <if test="columnId != null and columnId != ''">
-                AND (d.category_id = #{columnId} OR cat.parent_ids LIKE CONCAT('%,', #{columnId}, ',%'))
+                AND (d.category_id = #{columnId} OR cat.parent_ids LIKE CONCAT('%,', #{columnId}, ',%') OR d.category_id = '10')
             </if>
             </if>
             <!-- 管理员权限:默认只查看有积分记录的用户 -->
             <!-- 管理员权限:默认只查看有积分记录的用户 -->
             <if test="isAdmin != null and isAdmin == 'admin'">
             <if test="isAdmin != null and isAdmin == 'admin'">
@@ -174,9 +189,9 @@
             LEFT JOIN sys_office o ON o.id = u.office_id
             LEFT JOIN sys_office o ON o.id = u.office_id
             <where>
             <where>
                 u.del_flag = '0'
                 u.del_flag = '0'
-                <!-- 按积分分类节点及其所有子节点筛选 -->
+                <!-- 按积分分类节点及其所有子节点筛选,始终包含扣分标准(id=10)的记录 -->
                 <if test="columnId != null and columnId != ''">
                 <if test="columnId != null and columnId != ''">
-                    AND (d.category_id = #{columnId} OR cat.parent_ids LIKE CONCAT('%,', #{columnId}, ',%'))
+                    AND (d.category_id = #{columnId} OR cat.parent_ids LIKE CONCAT('%,', #{columnId}, ',%') OR d.category_id = '10')
                 </if>
                 </if>
                 <!-- 管理员权限:默认只查看有积分记录的用户 -->
                 <!-- 管理员权限:默认只查看有积分记录的用户 -->
                 <if test="isAdmin != null and isAdmin == 'admin'">
                 <if test="isAdmin != null and isAdmin == 'admin'">
@@ -252,9 +267,9 @@
             <if test="ruleType != null">
             <if test="ruleType != null">
                 AND r.rule_type = #{ruleType}
                 AND r.rule_type = #{ruleType}
             </if>
             </if>
-            <!-- 按积分分类节点及其所有子节点筛选 -->
+            <!-- 按积分分类节点及其所有子节点筛选,始终包含扣分标准(id=10)的记录 -->
             <if test="columnId != null and columnId != ''">
             <if test="columnId != null and columnId != ''">
-                AND (d.category_id = #{columnId} OR c.parent_ids LIKE CONCAT('%,', #{columnId}, ',%'))
+                AND (d.category_id = #{columnId} OR c.parent_ids LIKE CONCAT('%,', #{columnId}, ',%') OR d.category_id = '10')
             </if>
             </if>
             <!-- 正确日期查询:使用 DATE() 函数 + 直接传 Date 类型 -->
             <!-- 正确日期查询:使用 DATE() 函数 + 直接传 Date 类型 -->
             <if test="beginDate != null">
             <if test="beginDate != null">
@@ -299,9 +314,9 @@
             <if test="ruleType != null">
             <if test="ruleType != null">
                 AND r.rule_type = #{ruleType}
                 AND r.rule_type = #{ruleType}
             </if>
             </if>
-            <!-- 按积分分类节点及其所有子节点筛选 -->
+            <!-- 按积分分类节点及其所有子节点筛选,始终包含扣分标准(id=10)的记录 -->
             <if test="columnId != null and columnId != ''">
             <if test="columnId != null and columnId != ''">
-                AND (d.category_id = #{columnId} OR c.parent_ids LIKE CONCAT('%,', #{columnId}, ',%'))
+                AND (d.category_id = #{columnId} OR c.parent_ids LIKE CONCAT('%,', #{columnId}, ',%') OR d.category_id = '10')
             </if>
             </if>
             <!-- 正确日期查询:使用 DATE() 函数 + 直接传 Date 类型 -->
             <!-- 正确日期查询:使用 DATE() 函数 + 直接传 Date 类型 -->
             <if test="beginDate != null">
             <if test="beginDate != null">
@@ -329,7 +344,7 @@
             d.del_flag = '0'
             d.del_flag = '0'
             AND d.user_id = #{userId}
             AND d.user_id = #{userId}
             <if test="columnId != null and columnId != ''">
             <if test="columnId != null and columnId != ''">
-                AND (d.category_id = #{columnId} OR c.parent_ids LIKE CONCAT('%,', #{columnId}, ',%'))
+                AND (d.category_id = #{columnId} OR c.parent_ids LIKE CONCAT('%,', #{columnId}, ',%') OR d.category_id = '10')
             </if>
             </if>
         </where>
         </where>
     </select>
     </select>
@@ -343,7 +358,7 @@
             d.del_flag = '0'
             d.del_flag = '0'
             AND d.user_id = #{userId}
             AND d.user_id = #{userId}
             <if test="columnId != null and columnId != ''">
             <if test="columnId != null and columnId != ''">
-                AND (d.category_id = #{columnId} OR c.parent_ids LIKE CONCAT('%,', #{columnId}, ',%'))
+                AND (d.category_id = #{columnId} OR c.parent_ids LIKE CONCAT('%,', #{columnId}, ',%') OR d.category_id = '10')
             </if>
             </if>
             <if test="pointType != null and pointType == 'acquire'">
             <if test="pointType != null and pointType == 'acquire'">
                 AND d.point &gt; 0
                 AND d.point &gt; 0
@@ -415,7 +430,7 @@
             FROM work_knowledge_base_point_detail d
             FROM work_knowledge_base_point_detail d
             LEFT JOIN work_knowledge_point_category c ON c.id = d.category_id AND c.del_flag = '0'
             LEFT JOIN work_knowledge_point_category c ON c.id = d.category_id AND c.del_flag = '0'
             WHERE d.del_flag = '0'
             WHERE d.del_flag = '0'
-              AND (d.category_id = '2' OR c.parent_ids LIKE '%,2,%')
+              AND (d.category_id = '2' OR c.parent_ids LIKE '%,2,%' OR d.category_id = '10')
             GROUP BY d.user_id
             GROUP BY d.user_id
         ) cp ON cp.user_id = u.id
         ) cp ON cp.user_id = u.id
         WHERE u.del_flag = '0'
         WHERE u.del_flag = '0'

+ 4 - 2
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBasePointRuleDao.xml

@@ -12,6 +12,7 @@
         a.point_mode   AS "pointMode",
         a.point_mode   AS "pointMode",
         a.point_min    AS "pointMin",
         a.point_min    AS "pointMin",
         a.point_max    AS "pointMax",
         a.point_max    AS "pointMax",
+        a.is_deduction AS "isDeduction",
         a.create_by    AS "createBy.id",
         a.create_by    AS "createBy.id",
         a.create_date  AS "createDate",
         a.create_date  AS "createDate",
         a.update_by    AS "updateBy.id",
         a.update_by    AS "updateBy.id",
@@ -105,10 +106,10 @@
 
 
     <insert id="insert">
     <insert id="insert">
         INSERT INTO work_knowledge_base_point_rule (
         INSERT INTO work_knowledge_base_point_rule (
-            id, rule_type, point_value, point_name, status, category_id, point_mode, point_min, point_max,
+            id, rule_type, point_value, point_name, status, category_id, point_mode, point_min, point_max, is_deduction,
             create_by, create_date, update_by, update_date, del_flag
             create_by, create_date, update_by, update_date, del_flag
         ) VALUES (
         ) VALUES (
-            #{id}, #{ruleType}, #{pointValue}, #{pointName}, #{status}, #{categoryId}, #{pointMode}, #{pointMin}, #{pointMax},
+            #{id}, #{ruleType}, #{pointValue}, #{pointName}, #{status}, #{categoryId}, #{pointMode}, #{pointMin}, #{pointMax}, #{isDeduction},
             #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{delFlag}
             #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{delFlag}
         )
         )
     </insert>
     </insert>
@@ -123,6 +124,7 @@
             point_mode  = #{pointMode},
             point_mode  = #{pointMode},
             point_min   = #{pointMin},
             point_min   = #{pointMin},
             point_max   = #{pointMax},
             point_max   = #{pointMax},
+            is_deduction = #{isDeduction},
             update_by   = #{updateBy.id},
             update_by   = #{updateBy.id},
             update_date = #{updateDate}
             update_date = #{updateDate}
         WHERE id = #{id}
         WHERE id = #{id}

+ 155 - 0
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBaseViolationRecordDao.xml

@@ -0,0 +1,155 @@
+<?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.WorkKnowledgeBaseViolationRecordDao">
+
+    <sql id="violationRecordColumns">
+        a.id                AS "id",
+        a.file_id           AS "fileId",
+        a.target_user_id    AS "targetUserId",
+        a.handle_type       AS "handleType",
+        a.original_points   AS "originalPoints",
+        a.point_multiplier  AS "pointMultiplier",
+        a.deducted_points   AS "deductedPoints",
+        a.handle_reason     AS "handleReason",
+        a.handle_user_id    AS "handleUserId",
+        a.handle_time       AS "handleTime",
+        a.original_audit_status AS "originalAuditStatus",
+        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>
+
+    <select id="get" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord">
+        SELECT <include refid="violationRecordColumns"/>,
+            tu.name AS "targetUserName",
+            hu.name AS "handleUserName",
+            s.name  AS "fileName"
+        FROM work_knowledge_base_violation_record a
+        LEFT JOIN sys_user tu ON tu.id = a.target_user_id
+        LEFT JOIN sys_user hu ON hu.id = a.handle_user_id
+        LEFT JOIN work_knowledge_base_share_info s ON s.id = a.file_id
+        WHERE a.id = #{id}
+    </select>
+
+    <select id="findList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord">
+        SELECT <include refid="violationRecordColumns"/>,
+            tu.name AS "targetUserName",
+            hu.name AS "handleUserName",
+            s.name  AS "fileName"
+        FROM work_knowledge_base_violation_record a
+        LEFT JOIN sys_user tu ON tu.id = a.target_user_id
+        LEFT JOIN sys_user hu ON hu.id = a.handle_user_id
+        LEFT JOIN work_knowledge_base_share_info s ON s.id = a.file_id
+        WHERE a.del_flag = #{DEL_FLAG_NORMAL}
+        <if test="fileId != null and fileId != ''">
+            AND a.file_id = #{fileId}
+        </if>
+        <if test="targetUserId != null and targetUserId != ''">
+            AND a.target_user_id = #{targetUserId}
+        </if>
+        ORDER BY a.create_date DESC
+    </select>
+
+    <select id="findAllList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord">
+        SELECT <include refid="violationRecordColumns"/>,
+            tu.name AS "targetUserName",
+            hu.name AS "handleUserName",
+            s.name  AS "fileName"
+        FROM work_knowledge_base_violation_record a
+        LEFT JOIN sys_user tu ON tu.id = a.target_user_id
+        LEFT JOIN sys_user hu ON hu.id = a.handle_user_id
+        LEFT JOIN work_knowledge_base_share_info s ON s.id = a.file_id
+        WHERE a.del_flag = #{DEL_FLAG_NORMAL}
+        ORDER BY a.create_date DESC
+    </select>
+
+    <select id="findByFileId" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord">
+        SELECT <include refid="violationRecordColumns"/>,
+            tu.name AS "targetUserName",
+            hu.name AS "handleUserName",
+            s.name  AS "fileName"
+        FROM work_knowledge_base_violation_record a
+        LEFT JOIN sys_user tu ON tu.id = a.target_user_id
+        LEFT JOIN sys_user hu ON hu.id = a.handle_user_id
+        LEFT JOIN work_knowledge_base_share_info s ON s.id = a.file_id
+        WHERE a.del_flag = '0'
+          AND a.file_id = #{fileId}
+        ORDER BY a.create_date DESC
+    </select>
+
+    <select id="countByFileId" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM work_knowledge_base_violation_record
+        WHERE del_flag = '0'
+          AND file_id = #{fileId}
+    </select>
+
+    <select id="findLatestByFileId" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord">
+        SELECT <include refid="violationRecordColumns"/>,
+            tu.name AS "targetUserName",
+            hu.name AS "handleUserName"
+        FROM work_knowledge_base_violation_record a
+        LEFT JOIN sys_user tu ON tu.id = a.target_user_id
+        LEFT JOIN sys_user hu ON hu.id = a.handle_user_id
+        WHERE a.del_flag = '0'
+          AND a.file_id = #{fileId}
+        ORDER BY a.create_date DESC
+        LIMIT 1
+    </select>
+
+    <select id="queryCount" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM work_knowledge_base_violation_record a
+        WHERE a.del_flag = #{DEL_FLAG_NORMAL}
+    </select>
+
+    <select id="findUniqueByProperty" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseViolationRecord">
+        SELECT <include refid="violationRecordColumns"/>
+        FROM work_knowledge_base_violation_record a
+        WHERE ${propertyName} = #{value} AND a.del_flag = '0'
+        LIMIT 1
+    </select>
+
+    <insert id="insert">
+        INSERT INTO work_knowledge_base_violation_record (
+            id, file_id, target_user_id, handle_type,
+            original_points, point_multiplier, deducted_points,
+            handle_reason, handle_user_id, handle_time, original_audit_status,
+            create_by, create_date, update_by, update_date, remarks, del_flag
+        ) VALUES (
+            #{id}, #{fileId}, #{targetUserId}, #{handleType},
+            #{originalPoints}, #{pointMultiplier}, #{deductedPoints},
+            #{handleReason}, #{handleUserId}, #{handleTime}, #{originalAuditStatus},
+            #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{remarks}, #{delFlag}
+        )
+    </insert>
+
+    <update id="update">
+        UPDATE work_knowledge_base_violation_record SET
+            handle_type       = #{handleType},
+            original_points   = #{originalPoints},
+            point_multiplier  = #{pointMultiplier},
+            deducted_points   = #{deductedPoints},
+            handle_reason     = #{handleReason},
+            handle_user_id    = #{handleUserId},
+            handle_time       = #{handleTime},
+            update_by         = #{updateBy.id},
+            update_date       = #{updateDate},
+            remarks           = #{remarks}
+        WHERE id = #{id}
+    </update>
+
+    <delete id="delete">
+        DELETE FROM work_knowledge_base_violation_record WHERE id = #{id}
+    </delete>
+
+    <update id="deleteByLogic">
+        UPDATE work_knowledge_base_violation_record SET
+            del_flag = #{DEL_FLAG_DELETE}
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 16 - 2
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBasePointRuleForm.jsp

@@ -97,9 +97,23 @@
             
             
             var isBasicPoints = (categoryName && categoryName.indexOf("基础积分") !== -1);
             var isBasicPoints = (categoryName && categoryName.indexOf("基础积分") !== -1);
             var isExchangePoints = (categoryName && categoryName.indexOf("兑换积分") !== -1);
             var isExchangePoints = (categoryName && categoryName.indexOf("兑换积分") !== -1);
-            console.log("是否为基础积分:", isBasicPoints, "是否为兑换积分:", isExchangePoints);
+            var isDeductionPoints = (categoryName && categoryName.indexOf("扣分标准") !== -1);
+            console.log("是否为基础积分:", isBasicPoints, "是否为兑换积分:", isExchangePoints, "是否为扣分标准:", isDeductionPoints);
             
             
-            if (isExchangePoints) {
+            if (isDeductionPoints) {
+                console.log("切换到扣分标准模式(单选框:违规扣分)");
+                // 显示单选框,隐藏下拉框
+                $("#ruleTypeRadio").show();
+                $("#ruleTypeSelect").hide();
+                
+                // 更新单选框选项为违规扣分(只有一个选项)
+                $("#ruleTypeRadio label:first-child").html('<input type="radio" name="ruleType" value="5" lay-filter="ruleTypeFilter" style="margin-right:5px;" checked/> 扣分');
+                $("#ruleTypeRadio label:last-child").html('');
+                
+                // 强制选中违规扣分
+                $("input[name='ruleType'][value='5']").prop("checked", true);
+                
+            } else if (isExchangePoints) {
                 console.log("切换到兑换积分模式(下拉框)");
                 console.log("切换到兑换积分模式(下拉框)");
                 // 隐藏单选框,显示下拉框
                 // 隐藏单选框,显示下拉框
                 $("#ruleTypeRadio").hide();
                 $("#ruleTypeRadio").hide();

+ 4 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBasePointUserDetail.jsp

@@ -144,6 +144,10 @@
 				}}
 				}}
 				,{field:'ruleType',align:'center', title: '获取方式', width:110,templet:function(d){
 				,{field:'ruleType',align:'center', title: '获取方式', width:110,templet:function(d){
 					var val = parseInt(d.point) || 0;
 					var val = parseInt(d.point) || 0;
+					// 先判断是否为扣分标准分类(违规扣分)
+					if (d.categoryName && d.categoryName.indexOf('扣分标准') !== -1) {
+						return '<span class="layui-badge layui-bg-black">扣分</span>';
+					}
 					if (val < 0) return '<span class="layui-badge layui-bg-gray">积分兑换</span>';
 					if (val < 0) return '<span class="layui-badge layui-bg-gray">积分兑换</span>';
 					var rt = parseInt(d.ruleType);
 					var rt = parseInt(d.ruleType);
 					if (rt === 1) return '<span class="layui-badge layui-bg-blue">创建</span>';
 					if (rt === 1) return '<span class="layui-badge layui-bg-blue">创建</span>';

File diff ditekan karena terlalu besar
+ 77 - 3
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareList.jsp


+ 173 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseViolationHandle.jsp

@@ -0,0 +1,173 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<html>
+<head>
+    <title>违规处理</title>
+    <meta name="decorator" content="default"/>
+    <script type="text/javascript" src="${ctxStatic}/layui/layui.js"></script>
+    <link rel='stylesheet' type="text/css" href="${ctxStatic}/layui/css/layui.css"/>
+    <script src="${ctxStatic}/common/html/js/script.js"></script>
+    <style>
+        .violation-form-container { padding: 20px 30px; }
+        .violation-form-container .layui-form-label { width: 100px; }
+        .violation-form-container .layui-input-block { margin-left: 130px; }
+        .info-row { padding: 8px 0; color: #666; font-size: 14px; }
+        .info-row label { color: #333; font-weight: bold; margin-right: 8px; }
+        .history-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
+        .history-table th, .history-table td { border: 1px solid #e6e6e6; padding: 8px 10px; text-align: center; font-size: 13px; }
+        .history-table th { background: #f2f2f2; color: #333; }
+        .deduction-preview { color: #ff5722; font-weight: bold; font-size: 16px; margin-top: 5px; }
+    </style>
+    <script type="text/javascript">
+        // 计算扣除积分预览
+        function calcDeduction() {
+            var multiplier = parseFloat($('#pointMultiplier').val());
+            var originalPoints = parseInt('${entity.auditPassCount}') || 0;
+            // 从服务端获取原始积分
+            var origPts = parseInt($('#originalPoints').val()) || 0;
+            if (isNaN(multiplier) || multiplier < 0) {
+                $('#deductionPreview').text('');
+                return;
+            }
+            if (multiplier === 0) {
+                $('#deductionPreview').text('不扣分');
+                return;
+            }
+            var deducted = Math.round(origPts * multiplier);
+            $('#deductionPreview').text('将扣除 ' + deducted + ' 分(原积分 ' + origPts + ' × ' + multiplier + '倍)');
+        }
+
+        function doSubmit() {
+            var pointMultiplier = $('#pointMultiplier').val();
+            var handleReason = $('#handleReason').val();
+
+            if (pointMultiplier === '' || isNaN(parseFloat(pointMultiplier)) || parseFloat(pointMultiplier) < 0) {
+                top.layer.msg('请输入有效的扣积分倍数(0或正数)', {icon: 0});
+                return false;
+            }
+            if (!handleReason || handleReason.trim() === '') {
+                top.layer.msg('请输入处理原因', {icon: 0});
+                return false;
+            }
+
+            var confirmMsg = '确认执行违规处理操作吗?\n';
+            confirmMsg += '处理方式:作废(文件将被逻辑删除,不可恢复)';
+            if (parseFloat(pointMultiplier) > 0) {
+                confirmMsg += '\n将扣除当事人积分。';
+            }
+
+            top.layer.confirm(confirmMsg, {icon: 3, title: '确认操作'}, function(index) {
+                top.layer.close(index);
+                $.ajax({
+                    type: 'POST',
+                    url: '${ctx}/workKnowledgeBase/share/violationHandleSave',
+                    data: {
+                        id: '${entity.id}',
+                        handleType: 'invalid',
+                        pointMultiplier: pointMultiplier,
+                        handleReason: $('#handleReason').val()
+                    },
+                    dataType: 'json',
+                    success: function(res) {
+                        if (res.success) {
+                            top.layer.msg(res.message || '违规处理成功', {icon: 1});
+                            setTimeout(function() {
+                                var parentWin = top.layer.getFrameIndex(window.name);
+                                top.layer.close(parentWin);
+                                if (window.parent && window.parent.search) {
+                                    window.parent.search();
+                                }
+                            }, 800);
+                        } else {
+                            top.layer.msg(res.message || '操作失败', {icon: 0});
+                        }
+                    },
+                    error: function() {
+                        top.layer.msg('请求失败', {icon: 2});
+                    }
+                });
+            });
+            return false;
+        }
+    </script>
+</head>
+<body>
+<div class="violation-form-container">
+    <input type="hidden" id="entityId" value="${entity.id}"/>
+    <input type="hidden" id="originalPoints" value="${originalPoints}"/>
+
+    <%-- ========== 文件信息 ========== --%>
+    <div class="layui-form-item">
+        <div class="info-row"><label>文件名称:</label><c:out value='${entity.name}'/></div>
+        <div class="info-row"><label>被处理人:</label>${targetUserName}</div>
+    </div>
+
+    <hr/>
+
+    <%-- ========== 处理历史 ========== --%>
+    <c:if test="${not empty historyRecords}">
+    <div class="layui-form-item">
+        <label class="layui-form-label" style="width:auto;font-weight:bold;">处理历史:</label>
+        <table class="history-table">
+            <thead>
+                <tr>
+                    <th>处理方式</th>
+                    <th>原积分</th>
+                    <th>扣分倍数</th>
+                    <th>扣除积分</th>
+                    <th>处理原因</th>
+                    <th>处理人</th>
+                    <th>处理时间</th>
+                </tr>
+            </thead>
+            <tbody>
+                <c:forEach items="${historyRecords}" var="rec">
+                <tr>
+                    <td>作废</td>
+                    <td>${rec.originalPoints}</td>
+                    <td>${rec.pointMultiplier}</td>
+                    <td style="color:#ff5722;">${rec.deductedPoints}</td>
+                    <td style="text-align:left;"><c:out value='${rec.handleReason}'/></td>
+                    <td><c:out value='${rec.handleUserName}'/></td>
+                    <td><fmt:formatDate value="${rec.handleTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
+                </tr>
+                </c:forEach>
+            </tbody>
+        </table>
+    </div>
+    <hr/>
+    </c:if>
+
+    <%-- ========== 违规处理表单 ========== --%>
+    <div class="layui-form-item">
+        <label class="layui-form-label"><span style="color:red;">*</span> 处理方式:</label>
+        <div class="layui-input-block">
+            <input type="hidden" id="handleType" value="invalid"/>
+            <div class="layui-input" style="background:#f5f5f5;color:#999;">作废(逻辑删除,不可恢复)</div>
+        </div>
+    </div>
+
+    <div class="layui-form-item">
+        <label class="layui-form-label"><span style="color:red;">*</span> 扣分倍数:</label>
+        <div class="layui-input-block">
+            <input type="number" id="pointMultiplier" class="layui-input" placeholder="0=不扣分, 1=扣原积分1倍, 2=扣2倍..." min="0" step="0.5" oninput="calcDeduction()"/>
+            <div id="deductionPreview" class="deduction-preview"></div>
+        </div>
+    </div>
+
+    <div class="layui-form-item">
+        <label class="layui-form-label"><span style="color:red;">*</span> 处理原因:</label>
+        <div class="layui-input-block">
+            <textarea id="handleReason" class="layui-textarea" placeholder="请输入处理原因" rows="3" style="margin-left:0;"></textarea>
+        </div>
+    </div>
+
+    <div style="color:#999;font-size:12px;padding:10px 0;margin-left:130px;">
+        说明:<br/>
+        1. 作废:文件将被逻辑删除,数据库中仍可查询,但前台不再展示。作废后不可恢复,创建人如需重新提交请新增一条记录。<br/>
+        2. 扣分倍数为0表示不扣分,为1表示扣除当事人从该文件获得的全部积分,为2表示扣除2倍积分(允许扣成负数)。<br/>
+        3. 其他人因该文件的点赞、审核、阅读积分不受影响。
+    </div>
+</div>
+</body>
+</html>