Jelajahi Sumber

知识库积分部分功能提交

徐滕 1 Minggu lalu
induk
melakukan
0e3be498e3
29 mengubah file dengan 3195 tambahan dan 68 penghapusan
  1. 467 0
      CLAUDE.md
  2. 36 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeExpertDao.java
  3. 40 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgePointCategoryDao.java
  4. 65 10
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBasePointRule.java
  5. 100 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeExpert.java
  6. 102 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgePointCategory.java
  7. 41 9
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBasePointRuleService.java
  8. 19 4
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBaseShareService.java
  9. 130 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeExpertService.java
  10. 122 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgePointCategoryService.java
  11. 40 5
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBasePointRuleController.java
  12. 25 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBaseShareController.java
  13. 156 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeExpertController.java
  14. 159 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgePointCategoryController.java
  15. 19 4
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBasePointRuleDao.xml
  16. 122 0
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeExpertDao.xml
  17. 110 0
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgePointCategoryDao.xml
  18. 185 0
      src/main/resources/mysqlDateBase/README_积分规则树形改造.md
  19. 47 0
      src/main/resources/mysqlDateBase/alter_work_knowledge_base_point_rule_tree_structure.sql
  20. 181 0
      src/main/resources/mysqlDateBase/init_work_knowledge_base_point_rule_data.sql
  21. 41 0
      src/main/resources/mysqlDateBase/point_rule_category_upgrade.sql
  22. 19 0
      src/main/resources/mysqlDateBase/work_knowledge_expert.sql
  23. 186 16
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBasePointRuleForm.jsp
  24. 58 10
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBasePointRuleList.jsp
  25. 18 10
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareList.jsp
  26. 244 0
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeExpertForm.jsp
  27. 274 0
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeExpertList.jsp
  28. 94 0
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgePointCategoryForm.jsp
  29. 95 0
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgePointCategoryList.jsp

+ 467 - 0
CLAUDE.md

@@ -0,0 +1,467 @@
+# CLAUDE.md
+
+## 项目概述
+
+| 项 | 值 |
+|---|---|
+| **项目名称** | jeeplus |
+| **技术栈** | Jeeplus + Spring 4.0.8 + MyBatis 3.2.8 + MySQL 5.7 + Layui 2.0.3 |
+| **打包方式** | WAR (`jeeplus-0.0.1-SNAPSHOT.war`) |
+| **Java** | JDK 7/8 |
+| **ORM** | MyBatis 3.2.8,XML 映射文件 |
+| **前端** | JSP + Layui 2.0.3 + jQuery,自定义 jeeplus 标签库 |
+| **工作流** | Activiti 5.x |
+| **权限** | Apache Shiro 1.2.3 |
+| **代码仓库** | Git,分支 `master` |
+
+## 核心配置文件
+
+- `jeeplus.properties` — 主配置(数据库、adminPath=`/a`、frontPath=`/f` 等)
+- `pom.xml` — Maven 依赖管理
+- Spring 配置位于 `src/main/resources/spring/` 目录
+
+## 项目包结构
+
+```
+com.jeeplus
+├── common/                          # 公共基础类
+│   ├── config/Global.java           # 全局配置常量
+│   ├── persistence/
+│   │   ├── BaseEntity.java          # Entity 基类 (id, currentUser, page, sqlMap, isNewRecord)
+│   │   ├── DataEntity.java          # 数据实体基类 (remarks, createBy, createDate, updateBy, updateDate, delFlag)
+│   │   ├── ActEntity.java           # 工作流实体基类 (扩展 DataEntity, 含 Act act)
+│   │   ├── CrudDao.java             # DAO 接口基类 (get, findList, insert, update, delete)
+│   │   ├── Page.java                # 分页对象
+│   │   └── annotation/MyBatisDao.java  # MyBatis DAO 标记注解
+│   ├── service/
+│   │   ├── BaseService.java         # Service 基类
+│   │   └── CrudService.java         # CRUD Service 基类 (泛型 D extends CrudDao, T extends DataEntity)
+│   ├── web/BaseController.java      # Controller 基类
+│   └── utils/                       # 工具类 (StringUtils, DateUtils, IdGen 等)
+├── modules/                         # 业务模块 (按功能分包)
+│   └── {moduleName}/
+│       ├── entity/                  # 实体类
+│       ├── dao/                     # DAO 接口
+│       ├── service/                 # Service 类
+│       ├── web/                     # Controller 类
+│       ├── enums/                   # 枚举类 (可选)
+│       ├── utils/                   # 模块工具类 (可选)
+│       └── thread/                  # 线程类 (可选)
+└── ...
+```
+
+## 各层编码规范
+
+### 1. Entity 层
+
+**文件位置**: `src/main/java/com/jeeplus/modules/{moduleName}/entity/{EntityName}.java`
+
+**规范要点**:
+- 普通业务实体继承 `DataEntity<T>`,含工作流的实体继承 `ActEntity<T>`
+- 必须提供两个构造函数:无参构造、`(String id)` 构造调用 `super(id)`
+- 所有字段为 `private`,提供 public getter/setter
+- 日期字段 getter 上加 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")`
+- 需要导出的字段 getter 上加 `@ExcelField(title="字段名", align=2, sort=序号)`
+- 字段注释放在字段声明同一行末尾,用 `// 注释`
+- 必须声明 `private static final long serialVersionUID = 1L;`
+- 关联对象以完整对象引用(如 `private WorkStaffBasicInfo basicInfo`)
+- 列表字段初始化用 `Lists.newArrayList()`(如 `private List<WorkClientAttachment> workAttachments = Lists.newArrayList()`)
+
+```java
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.modules.{moduleName}.entity;
+
+// imports...
+
+/**
+ * {模块中文名}Entity
+ * @author {作者}
+ * @version {日期}
+ */
+public class {EntityName} extends DataEntity<{EntityName}> {
+    
+    private static final long serialVersionUID = 1L;
+    private String field1;      // 字段注释
+    private Date field2;        // 字段注释
+    
+    public {EntityName}() {
+        super();
+    }
+    
+    public {EntityName}(String id){
+        super(id);
+    }
+    
+    @ExcelField(title="字段名", align=2, sort=7)
+    public String getField1() { return field1; }
+    public void setField1(String field1) { this.field1 = field1; }
+    
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ExcelField(title="日期字段", align=2, sort=8)
+    public Date getField2() { return field2; }
+    public void setField2(Date field2) { this.field2 = field2; }
+}
+```
+
+### 2. DAO 层
+
+**文件位置**: `src/main/java/com/jeeplus/modules/{moduleName}/dao/{EntityName}Dao.java`
+
+**规范要点**:
+- 接口必须加 `@MyBatisDao` 注解
+- 必须继承 `CrudDao<T>` 接口
+- 不使用 `implements`,只声明自定义方法
+- 自定义方法的参数复杂时用 `@Param` 注解
+
+```java
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.modules.{moduleName}.dao;
+
+import com.jeeplus.common.persistence.CrudDao;
+import com.jeeplus.common.persistence.annotation.MyBatisDao;
+import com.jeeplus.modules.{moduleName}.entity.{EntityName};
+
+/**
+ * {模块中文名}DAO接口
+ * @author {作者}
+ * @version {日期}
+ */
+@MyBatisDao
+public interface {EntityName}Dao extends CrudDao<{EntityName}> {
+    // 自定义查询方法
+    public List<{EntityName}> getByXxx(@Param("param1")String param1);
+}
+```
+
+### 3. MyBatis XML 映射
+
+**文件位置**: `src/main/resources/mappings/modules/{moduleName}/{EntityName}Dao.xml`
+
+**规范要点**:
+- `namespace` 必须等于 DAO 接口全限定名
+- 定义 `<sql id="{entityName}Columns">` 和 `<sql id="{entityName}Joins">` 片段
+- 列别名使用双引号(如 `a.field AS "field"`)
+- 关联嵌套对象使用点分隔别名(如 `w.name AS "basicInfo.name"`)
+- CRUD 标准方法:`get`, `findList`, `findAllList`, `insert`, `update`, `delete`, `deleteByLogic`, `findUniqueByProperty`
+- 分页排序使用 `page.orderBy` 动态 ORDER BY
+- 条件判断使用 `<if test="... != null and ... != ''"> `
+- 多数据库兼容写法:`<if test="dbName == 'mysql'">` / `oracle` / `mssql`
+
+```xml
+<?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.{moduleName}.dao.{EntityName}Dao">
+    
+    <sql id="{entityName}Columns">
+        a.id AS "id",
+        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",
+        a.field1 AS "field1",
+        a.field2 AS "field2"
+    </sql>
+    
+    <sql id="{entityName}Joins">
+        LEFT JOIN xxx_table x ON x.id = a.xxx_id
+        LEFT JOIN sys_office o ON o.id = a.office_id
+        LEFT JOIN sys_user u ON u.id = a.create_by
+    </sql>
+    
+    <select id="get" resultType="{EntityName}">
+        SELECT <include refid="{entityName}Columns"/>
+        FROM {table_name} a
+        <include refid="{entityName}Joins"/>
+        WHERE a.id = #{id}
+    </select>
+    
+    <select id="findList" resultType="{EntityName}">
+        SELECT <include refid="{entityName}Columns"/>
+        FROM {table_name} a
+        <include refid="{entityName}Joins"/>
+        <where>
+            a.del_flag = #{DEL_FLAG_NORMAL}
+            <if test="field1 != null and field1 != ''">
+                AND a.field1 = #{field1}
+            </if>
+        </where>
+        <choose>
+            <when test="page !=null and page.orderBy != null and page.orderBy != ''">
+                ORDER BY ${page.orderBy}
+            </when>
+            <otherwise>
+                ORDER BY a.update_date DESC
+            </otherwise>
+        </choose>
+    </select>
+    
+    <insert id="insert">
+        INSERT INTO {table_name}(id, create_by, create_date, update_by, update_date, remarks, del_flag, field1, field2)
+        VALUES (#{id}, #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{remarks}, #{delFlag}, #{field1}, #{field2})
+    </insert>
+    
+    <update id="update">
+        UPDATE {table_name} SET 
+            update_by = #{updateBy.id}, update_date = #{updateDate}, remarks = #{remarks},
+            field1 = #{field1}, field2 = #{field2}
+        WHERE id = #{id}
+    </update>
+    
+    <update id="delete">
+        DELETE FROM {table_name} WHERE id = #{id}
+    </update>
+    
+    <update id="deleteByLogic">
+        UPDATE {table_name} SET del_flag = #{DEL_FLAG_DELETE} WHERE id = #{id}
+    </update>
+    
+    <select id="findUniqueByProperty" resultType="{EntityName}" statementType="STATEMENT">
+        select * FROM {table_name} where ${propertyName} = '${value}'
+    </select>
+</mapper>
+```
+
+### 4. Service 层
+
+**文件位置**: `src/main/java/com/jeeplus/modules/{moduleName}/service/{EntityName}Service.java`
+
+**规范要点**:
+- 类上加 `@Service` 和 `@Transactional(readOnly = true)`
+- 必须继承 `CrudService<{EntityName}Dao, {EntityName}>`
+- 写操作方法加 `@Transactional(readOnly = false)`
+- 注入 DAO 使用 `@Autowired`,也可直接使用父类注入的 `dao` 字段
+- 数据权限过滤使用 `dataScopeFilter()` 放入 `sqlMap.dsf`
+- 覆盖 `save()` 方法时必须调用 `super.save(entity)` 并处理子表
+
+```java
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.modules.{moduleName}.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.service.CrudService;
+import com.jeeplus.modules.{moduleName}.entity.{EntityName};
+import com.jeeplus.modules.{moduleName}.dao.{EntityName}Dao;
+
+/**
+ * {模块中文名}Service
+ * @author {作者}
+ * @version {日期}
+ */
+@Service
+@Transactional(readOnly = true)
+public class {EntityName}Service extends CrudService<{EntityName}Dao, {EntityName}> {
+    
+    public {EntityName} get(String id) {
+        return super.get(id);
+    }
+    
+    public List<{EntityName}> findList({EntityName} entity) {
+        return super.findList(entity);
+    }
+    
+    public Page<{EntityName}> findPage(Page<{EntityName}> page, {EntityName} entity) {
+        return super.findPage(page, entity);
+    }
+    
+    @Transactional(readOnly = false)
+    public void save({EntityName} entity) {
+        super.save(entity);
+    }
+    
+    @Transactional(readOnly = false)
+    public void delete({EntityName} entity) {
+        super.delete(entity);
+    }
+}
+```
+
+### 5. Controller 层
+
+**文件位置**: `src/main/java/com/jeeplus/modules/{moduleName}/web/{EntityName}Controller.java`
+
+**规范要点**:
+- `@Controller` + `@RequestMapping(value = "${adminPath}/{moduleName}/{entityName}")`
+- 必须继承 `BaseController`,不能继承其他 Controller
+- 使用 `@ModelAttribute` 方法根据 `id` 参数获取实体
+- 方法顺序:`list` → `form` → `save` → `delete` → `export/import` → 自定义
+- 列表方法需加 `@RequiresPermissions("{moduleName}:{entityName}:list")`
+- 返回视图路径格式:`modules/{moduleName}/{entityName}List` / `Form`
+- 保存成功后 `redirect` 回列表页,使用 `Global.getAdminPath()` 获取管理路径前缀
+- 使用 `addMessage(redirectAttributes, "消息")` 传递消息
+- 编辑保存前使用 `MyBeanUtils.copyBeanNotNull2Bean(formObj, dbObj)` 合并数据
+
+```java
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.modules.{moduleName}.web;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.web.BaseController;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.modules.{moduleName}.entity.{EntityName};
+import com.jeeplus.modules.{moduleName}.service.{EntityName}Service;
+
+/**
+ * {模块中文名}Controller
+ * @author {作者}
+ * @version {日期}
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/{moduleName}/{entityName}")
+public class {EntityName}Controller extends BaseController {
+
+    @Autowired
+    private {EntityName}Service {entityName}Service;
+    @Autowired
+    private HttpServletRequest request;
+
+    @ModelAttribute
+    public {EntityName} get(@RequestParam(required=false) String id) {
+        {EntityName} entity = null;
+        if (StringUtils.isNotBlank(id)){
+            entity = {entityName}Service.get(id);
+        }
+        if (entity == null){
+            entity = new {EntityName}();
+        }
+        return entity;
+    }
+
+    @RequiresPermissions("{moduleName}:{entityName}:list")
+    @RequestMapping(value = {"list", ""})
+    public String list({EntityName} entity, HttpServletRequest request, HttpServletResponse response, Model model) {
+        Page<{EntityName}> page = {entityName}Service.findPage(new Page<{EntityName}>(request, response), entity);
+        model.addAttribute("page", page);
+        return "modules/{moduleName}/{entityName}List";
+    }
+
+    @RequestMapping(value = "form")
+    public String form({EntityName} entity, Model model) {
+        model.addAttribute("{entityName}", entity);
+        return "modules/{moduleName}/{entityName}Form";
+    }
+
+    @RequestMapping(value = "save")
+    public String save({EntityName} entity, Model model, RedirectAttributes redirectAttributes) throws Exception {
+        if (!beanValidator(model, entity)){
+            return form(entity, model);
+        }
+        if (!entity.getIsNewRecord()){
+            {EntityName} t = {entityName}Service.get(entity.getId());
+            MyBeanUtils.copyBeanNotNull2Bean(entity, t);
+            {entityName}Service.save(t);
+        } else {
+            {entityName}Service.save(entity);
+        }
+        addMessage(redirectAttributes, "保存成功");
+        return "redirect:" + Global.getAdminPath() + "/{moduleName}/{entityName}/?repage";
+    }
+
+    @RequestMapping(value = "delete")
+    public String delete({EntityName} entity, RedirectAttributes redirectAttributes) {
+        {entityName}Service.delete(entity);
+        addMessage(redirectAttributes, "删除成功");
+        return "redirect:" + Global.getAdminPath() + "/{moduleName}/{entityName}/?repage";
+    }
+}
+```
+
+### 6. 前端 JSP 页面
+
+**文件位置**: `src/main/webapp/webpage/modules/{moduleName}/{entityName}List.jsp` / `{entityName}Form.jsp` / `{entityName}View.jsp`
+
+**规范要点**:
+- 使用 `<jsp:include>` 和 Jeeplus 自定义标签库(`taglib.jsp` 引入)
+- 表单使用 `<form:form>` 标签,`modelAttribute` 绑定后端对象
+- `${ctx}` 获取上下文路径(来自 `jeeplus.properties` 的 `adminPath`)
+- 列表页使用 Layui 的 oa-table 表格:`<table class="oa-table layui-table" id="contentTable"></table>`
+- 分页使用 `<table:page page="${page}"></table:page>` 标签
+- 排序使用 `<table:sortColumn id="orderBy" name="orderBy" value="${page.orderBy}" callback="sortOrRefresh();"/>`
+- 按钮样式类:`nav-btn nav-btn-add`、`nav-btn-refresh`、`nav-btn-import`、`nav-btn-export`
+- 弹窗使用 `openDialogAdd()`、`openDialogEdit()`、`openDialogView()` 函数
+- 后端消息使用 `<sys:message content="${message}"/>`
+- 表单验证使用 jQuery Validate
+- 文件上传限制检查:单文件限制 50MB,总文件限制 500MB
+- 列表 JS 初始化方式:
+
+```javascript
+$(document).ready(function() {
+    // 表格列配置
+    var cols = [
+        {field:'no', title:'编号', sortable: true, width:100},
+        {field:'name', title:'名称', sortable: true},
+        {field:'createDate', title:'创建时间', sortable: true, width:150},
+        {fixed:'right', title:'操作', toolbar:'#operating', width:180}
+    ];
+    var table = new TableInit("{entityName}", cols, '${ctx}/{moduleName}/{entityName}', 
+        "searchForm", true, "contentTable", "operating");
+    table.init();
+});
+```
+
+**关键 JavaScript 组件构造器**:
+- `TableInit(tableName, cols, url, formId, checkFlag, tableId, toolId)` — 初始化表格
+- `TableTreeInit(tableName, cols, url, formId, tableId, toolId, parentField)` — 树形表格
+- `DetailTableInit(detailName, fieldName, cols, url, tableId, toolId, obj)` — 子表
+
+**Layui 日期控件**: 使用 `lay('.di').each(function(){ laydate.render({ elem: this, type: 'date', trigger: 'click' });})`
+
+## 全局常量
+
+- `BaseEntity.DEL_FLAG_NORMAL = "0"` / `DEL_FLAG_DELETE = "1"` / `DEL_FLAG_AUDIT = "2"`
+- `Global.getAdminPath()` — 管理端根路径(默认 `/a`)
+- `${ctx}` — JSP 中引用 adminPath
+- `UserUtils.getUser()` — 获取当前登录用户
+- `UserUtils.getSelectCompany()` — 获取当前选中公司
+- `UserUtils.getSelectOffice()` — 获取当前选中部门
+
+## 文件命名规则
+
+| 类型 | 命名模式 | 示例 |
+|------|----------|------|
+| Entity | `{EntityName}.java` | `LeaveApply.java` |
+| DAO 接口 | `{EntityName}Dao.java` | `LeaveApplyDao.java` |
+| DAO XML | `{EntityName}Dao.xml` | `LeaveApplyDao.xml` |
+| Service | `{EntityName}Service.java` | `LeaveApplyService.java` |
+| Controller | `{EntityName}Controller.java` | `LeaveApplyController.java` |
+| 列表页 | `{entityName}List.jsp` | `leaveApplyList.jsp` |
+| 表单页 | `{entityName}Form.jsp` | `leaveApplyForm.jsp` |
+| 查看页 | `{entityName}View.jsp` | `leaveApplyView.jsp` |
+| 子表页 | `{childName}List.jsp` | `leaveDetailList.jsp` |
+
+## 数据库表命名
+
+- 表名使用下划线小写:`{module_name}` 如 `leave_apply`、`leave_detail`
+- 关联外键命名:`{ref_table}_id` 如 `staff_id`、`leave_id`
+- 必备列:`id`(varchar 64), `create_by`, `create_date`, `update_by`, `update_date`, `remarks`, `del_flag`
+
+## 其他注意事项
+
+1. **文件头版权声明**: 所有 Java/JSP 文件必须保留 JeePlus 版权声明
+2. **缩进**: 使用 Tab 缩进(4 空格宽度)
+3. **日期格式**: Java 端 `yyyy-MM-dd HH:mm:ss`,前端 `yyyy-MM-dd`(日期控件)
+4. **代码中字符串比较**用 `StringUtils.isNotBlank()`/`isBlank()` 而非 `!= null && !""`
+5. **权限注解**: Shiro 的 `@RequiresPermissions("module:entity:action")`
+6. **JSON 序列化忽略**: 对不需要 JSON 序列化的字段使用 `@JsonIgnore` 或 `@JSONField(serialize = false)`
+7. **import 语句**会包含 `com.google.common.collect.Lists`、`com.google.common.collect.Maps`
+8. **Controller 基类**注入 `HttpServletRequest request` 用于获取当前请求
+9. **Excel 导出**使用 `ExportExcel`/`ImportExcel` 工具类
+10. **子表处理**:在 Service 的 save 中先 `super.save(entity)` 再分别处理子表 CRUD

+ 36 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeExpertDao.java

@@ -0,0 +1,36 @@
+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.WorkKnowledgeExpert;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 知识库专家审核人员 DAO
+ * @author: System
+ * @version: 2026-06-16
+ */
+@MyBatisDao
+public interface WorkKnowledgeExpertDao extends CrudDao<WorkKnowledgeExpert> {
+
+    /**
+     * 查询所有启用的专家(用于下拉选择)
+     */
+    List<WorkKnowledgeExpert> findEnabledList();
+
+    /**
+     * 根据用户ID检查是否已绑定专家
+     * @param userId 系统用户ID
+     * @return 专家记录数
+     */
+    int countByUserId(@Param("userId") String userId);
+
+    /**
+     * 根据用户ID查询专家信息
+     * @param userId 系统用户ID
+     * @return 专家实体
+     */
+    WorkKnowledgeExpert getByUserId(@Param("userId") String userId);
+}

+ 40 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgePointCategoryDao.java

@@ -0,0 +1,40 @@
+package com.jeeplus.modules.WorkKnowledgeBase.dao;
+
+import com.jeeplus.common.persistence.CrudDao;
+import com.jeeplus.common.persistence.TreeDao;
+import com.jeeplus.common.persistence.annotation.MyBatisDao;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 积分分类树形 DAO
+ * @author: 徐滕
+ * @version: 2026-06-16
+ */
+@MyBatisDao
+public interface WorkKnowledgePointCategoryDao extends CrudDao<WorkKnowledgePointCategory>, TreeDao<WorkKnowledgePointCategory> {
+
+    /**
+     * 查询所有启用的分类(用于下拉选择)
+     */
+    List<WorkKnowledgePointCategory> findEnabledList();
+
+    /**
+     * 根据parentIds查询所有子分类(用于删除校验)
+     */
+    List<WorkKnowledgePointCategory> findByParentIdsLike(@Param("parentIds") String parentIds);
+
+    /**
+     * 更新分类的parentIds
+     */
+    int updateParentIds(@Param("id") String id, @Param("parentIds") String parentIds, @Param("updateBy") String updateBy);
+
+    /**
+     * 批量更新子节点的parentIds(递归刷新链路)
+     */
+    int batchUpdateParentIds(@Param("oldParentIdsPrefix") String oldParentIdsPrefix,
+                             @Param("newParentIdsPrefix") String newParentIdsPrefix,
+                             @Param("updateBy") String updateBy);
+}

+ 65 - 10
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBasePointRule.java

@@ -10,30 +10,45 @@ import com.jeeplus.common.persistence.DataEntity;
 public class WorkKnowledgeBasePointRule extends DataEntity<WorkKnowledgeBasePointRule> {
 
     /** 规则类型 1:创建数据积分 2:审核通过积分 */
-    private Integer ruleType;
+    private String ruleType;
 
-    /** 积分值 */
-    private Integer pointValue;
+    /** 积分值(固定分值模式) */
+    private String pointValue;
 
     /** 积分规则名称(手动填写,用于列表展示) */
     private String pointName;
 
     /** 状态 1启用 0禁用 */
-    private Integer status;
+    private String status;
 
-    public Integer getRuleType() {
+    /** 归属分类ID,关联分类表主键 */
+    private String categoryId;
+
+    /** 分值模式 1固定分值 2区间分值 */
+    private String pointMode;
+
+    /** 区间积分下限(point_mode=2生效) */
+    private String pointMin;
+
+    /** 区间积分上限(point_mode=2生效) */
+    private String pointMax;
+
+    /** 归属分类名称(关联查询,非数据库字段) */
+    private String categoryName;
+
+    public String getRuleType() {
         return ruleType;
     }
 
-    public void setRuleType(Integer ruleType) {
+    public void setRuleType(String ruleType) {
         this.ruleType = ruleType;
     }
 
-    public Integer getPointValue() {
+    public String getPointValue() {
         return pointValue;
     }
 
-    public void setPointValue(Integer pointValue) {
+    public void setPointValue(String pointValue) {
         this.pointValue = pointValue;
     }
 
@@ -45,11 +60,51 @@ public class WorkKnowledgeBasePointRule extends DataEntity<WorkKnowledgeBasePoin
         this.pointName = pointName;
     }
 
-    public Integer getStatus() {
+    public String getStatus() {
         return status;
     }
 
-    public void setStatus(Integer status) {
+    public void setStatus(String status) {
         this.status = status;
     }
+
+    public String getCategoryId() {
+        return categoryId;
+    }
+
+    public void setCategoryId(String categoryId) {
+        this.categoryId = categoryId;
+    }
+
+    public String getPointMode() {
+        return pointMode;
+    }
+
+    public void setPointMode(String pointMode) {
+        this.pointMode = pointMode;
+    }
+
+    public String getPointMin() {
+        return pointMin;
+    }
+
+    public void setPointMin(String pointMin) {
+        this.pointMin = pointMin;
+    }
+
+    public String getPointMax() {
+        return pointMax;
+    }
+
+    public void setPointMax(String pointMax) {
+        this.pointMax = pointMax;
+    }
+
+    public String getCategoryName() {
+        return categoryName;
+    }
+
+    public void setCategoryName(String categoryName) {
+        this.categoryName = categoryName;
+    }
 }

+ 100 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeExpert.java

@@ -0,0 +1,100 @@
+package com.jeeplus.modules.WorkKnowledgeBase.entity;
+
+import com.jeeplus.common.persistence.DataEntity;
+
+/**
+ * 知识库专家审核人员实体
+ * @author: System
+ * @version: 2026-06-16
+ */
+public class WorkKnowledgeExpert extends DataEntity<WorkKnowledgeExpert> {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 关联系统用户ID(sys_user.id) */
+    private String userId;
+
+    /** 专家真实姓名 */
+    private String expertName;
+
+    /** 专业领域(技术总结/培训心得) */
+    private String professionalField;
+
+    /** 联系电话 */
+    private String phone;
+
+    /** 专家简介、擅长审核文档类型 */
+    private String introduction;
+
+    /** 状态 1启用(可接收审核任务) 0禁用 */
+    private String status;
+
+    /** 排序号,数值越小越靠前 */
+    private String sort;
+
+    public WorkKnowledgeExpert() {
+        super();
+        this.status = "1";
+        this.sort = "0";
+    }
+
+    public WorkKnowledgeExpert(String id) {
+        super(id);
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getExpertName() {
+        return expertName;
+    }
+
+    public void setExpertName(String expertName) {
+        this.expertName = expertName;
+    }
+
+    public String getProfessionalField() {
+        return professionalField;
+    }
+
+    public void setProfessionalField(String professionalField) {
+        this.professionalField = professionalField;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getIntroduction() {
+        return introduction;
+    }
+
+    public void setIntroduction(String introduction) {
+        this.introduction = introduction;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getSort() {
+        return sort;
+    }
+
+    public void setSort(String sort) {
+        this.sort = sort;
+    }
+}

+ 102 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgePointCategory.java

@@ -0,0 +1,102 @@
+package com.jeeplus.modules.WorkKnowledgeBase.entity;
+
+import com.fasterxml.jackson.annotation.JsonBackReference;
+import com.jeeplus.common.persistence.TreeEntity;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 积分分类树形实体(页面左侧树专用)
+ * @author: 徐滕
+ * @version: 2026-06-16
+ */
+public class WorkKnowledgePointCategory extends TreeEntity<WorkKnowledgePointCategory> {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 父分类对象 */
+    private WorkKnowledgePointCategory parent;
+
+    /** 父分类ID(数据库字段) */
+    private String parentId;
+
+    /** 所有父级编号(逗号分隔) */
+    private String parentIds;
+
+    /** 分类名称 */
+    private String categoryName;
+
+    /** 排序号 */
+    private Integer sort;
+
+    /** 状态 1启用 0禁用 */
+    private String status;
+
+    public WorkKnowledgePointCategory() {
+        super();
+        this.sort = 30;
+        this.status = "1";
+    }
+
+    public WorkKnowledgePointCategory(String id) {
+        super(id);
+    }
+
+    @JsonBackReference
+    @NotNull
+    public WorkKnowledgePointCategory getParent() {
+        return parent;
+    }
+
+    public void setParent(WorkKnowledgePointCategory parent) {
+        this.parent = parent;
+    }
+
+    public String getParentId() {
+        return parentId != null ? parentId : "0";
+    }
+
+    public void setParentId(String parentId) {
+        this.parentId = parentId;
+    }
+
+    public String getCategoryName() {
+        return categoryName;
+    }
+
+    public void setCategoryName(String categoryName) {
+        this.categoryName = categoryName;
+    }
+
+    public Integer getSort() {
+        return sort;
+    }
+
+    public void setSort(Integer sort) {
+        this.sort = sort;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getParentIds() {
+        return parentIds;
+    }
+
+    public void setParentIds(String parentIds) {
+        this.parentIds = parentIds;
+    }
+
+    /**
+     * 递归更新自身及所有子节点的parentIds链路
+     * @param newParentIds 新的父节点链路
+     */
+    public void updateParentIdsRecursive(String newParentIds) {
+        this.parentIds = newParentIds + this.getId() + ",";
+    }
+}

+ 41 - 9
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBasePointRuleService.java

@@ -39,7 +39,10 @@ public class WorkKnowledgeBasePointRuleService extends CrudService<WorkKnowledge
     /**
      * 保存(新增/更新)
      * 校验:
-     *  - pointName / pointValue 必填
+     *  - pointName 必填
+     *  - categoryId 必填(历史存量数据编辑时必须选择分类)
+     *  - point_mode=1:pointValue 必填且为正整数,pointMin、pointMax 置空
+     *  - point_mode=2:pointMin、pointMax 必填且为正整数且 min <= max,pointValue 置空
      *  - 启用状态下同名称只能存在一条
      * @return 错误信息,null 表示成功
      */
@@ -51,14 +54,43 @@ public class WorkKnowledgeBasePointRuleService extends CrudService<WorkKnowledge
         if (StringUtils.isBlank(entity.getPointName())) {
             return "规则名称不能为空";
         }
-        if (entity.getPointValue() == null || entity.getPointValue() < 0) {
-            return "积分值必须为非负整数";
+        // 分类必填校验(历史存量数据兼容)
+        if (StringUtils.isBlank(entity.getCategoryId())) {
+            return "必须选择归属分类";
+        }
+        // 分值模式校验
+        if (StringUtils.isBlank(entity.getPointMode())) {
+            entity.setPointMode("1"); // 默认固定分值
+        }
+        if ("1".equals(entity.getPointMode())) {
+            // 固定分值模式
+            if (StringUtils.isBlank(entity.getPointValue()) || !entity.getPointValue().matches("^[0-9]+$")) {
+                return "固定分值模式下,积分值必须为正整数";
+            }
+            entity.setPointMin(null);
+            entity.setPointMax(null);
+        } else if ("2".equals(entity.getPointMode())) {
+            // 区间分值模式
+            if (StringUtils.isBlank(entity.getPointMin()) || !entity.getPointMin().matches("^[0-9]+$")) {
+                return "区间分值模式下,积分下限必须为正整数";
+            }
+            if (StringUtils.isBlank(entity.getPointMax()) || !entity.getPointMax().matches("^[0-9]+$")) {
+                return "区间分值模式下,积分上限必须为正整数";
+            }
+            int min = Integer.parseInt(entity.getPointMin());
+            int max = Integer.parseInt(entity.getPointMax());
+            if (min > max) {
+                return "积分下限不能大于上限";
+            }
+            entity.setPointValue(null);
+        } else {
+            return "分值模式参数异常";
         }
         if (entity.getStatus() == null) {
-            entity.setStatus(1);
+            entity.setStatus("1");
         }
         // 唯一性校验:启用态同名称唯一
-        if (entity.getStatus() != null && entity.getStatus() == 1) {
+        if ("1".equals(entity.getStatus())) {
             int dup = pointRuleDao.countEnabledByPointName(entity.getPointName().trim(), entity.getId());
             if (dup > 0) {
                 return "该规则名称已存在启用记录,请先禁用原有记录或直接编辑";
@@ -85,10 +117,10 @@ public class WorkKnowledgeBasePointRuleService extends CrudService<WorkKnowledge
 
     /** 切换状态 */
     @Transactional(readOnly = false)
-    public String toggleStatus(String id, Integer status) {
-        if (StringUtils.isBlank(id) || status == null) return "参数缺失";
+    public String toggleStatus(String id, String status) {
+        if (StringUtils.isBlank(id) || StringUtils.isBlank(status)) return "参数缺失";
         // 启用前唯一性校验(同名称启用态唯一)
-        if (status == 1) {
+        if ("1".equals(status)) {
             WorkKnowledgeBasePointRule current = pointRuleDao.get(id);
             if (current == null) return "记录不存在";
             int dup = pointRuleDao.countEnabledByPointName(current.getPointName(), id);
@@ -98,7 +130,7 @@ public class WorkKnowledgeBasePointRuleService extends CrudService<WorkKnowledge
         }
         User user = UserUtils.getUser();
         String updateBy = user != null ? user.getId() : "";
-        pointRuleDao.updateStatus(id, status, updateBy);
+        pointRuleDao.updateStatus(id, Integer.parseInt(status), updateBy);
         return null;
     }
 }

+ 19 - 4
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBaseShareService.java

@@ -11,6 +11,7 @@ import com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeBasePointDetailDao
 import com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeBasePointRuleDao;
 import com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeBaseShareInfoDao;
 import com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeBaseTreeInfoDao;
+import com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeExpertDao;
 import com.jeeplus.modules.WorkKnowledgeBase.entity.*;
 import com.jeeplus.modules.sys.entity.User;
 import com.jeeplus.modules.sys.entity.Workattachment;
@@ -83,6 +84,9 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
 
     @Autowired
     private WorkKnowledgeBasePointRuleDao pointRuleDao;
+    
+    @Autowired
+    private WorkKnowledgeExpertDao expertDao;
 
     /**
      * 根据文件id加载附件列表(编辑表单回显用)
@@ -123,6 +127,16 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
         }
         return dynamicInfoDao.findByRootId(rootId);
     }
+    
+    /**
+     * 根据节点ID查询树节点信息
+     */
+    public WorkKnowledgeBaseTreeInfo getTreeInfoById(String treeNodeId) {
+        if (StringUtils.isBlank(treeNodeId)) {
+            return null;
+        }
+        return treeInfoDao.get(treeNodeId);
+    }
 
     /**
      * 动态列表分页查询(核心方法)
@@ -445,6 +459,7 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
         dao.updateAuditStatus(entity);
     }
 
+
     /**
      * 判断当前用户是否可以编辑指定文件
      */
@@ -533,13 +548,13 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
         String submitUserId = entity.getSubmitAuditUserId();
         if (StringUtils.isNotBlank(submitUserId)) {
             WorkKnowledgeBasePointRule createRule = pointRuleDao.findByRuleType(POINT_RULE_CREATE);
-            if (createRule != null && createRule.getPointValue() != null && createRule.getPointValue() > 0) {
-                insertPointDetail(submitUserId, shareId, POINT_CHANGE_CREATE, createRule.getPointValue());
+            if (createRule != null && createRule.getPointValue() != null && Integer.parseInt(createRule.getPointValue()) > 0) {
+                insertPointDetail(submitUserId, shareId, POINT_CHANGE_CREATE, Integer.parseInt(createRule.getPointValue()));
             }
         }
         // 2、本轮三位审核人各获得审核积分(仅统计 audit_result=1 且 audit_time >= startTime)
         WorkKnowledgeBasePointRule auditRule = pointRuleDao.findByRuleType(POINT_RULE_AUDIT);
-        if (auditRule == null || auditRule.getPointValue() == null || auditRule.getPointValue() <= 0) {
+        if (auditRule == null || auditRule.getPointValue() == null || Integer.parseInt(auditRule.getPointValue()) <= 0) {
             return;
         }
         List<WorkKnowledgeBaseAuditRecord> records = auditRecordDao.findByFileIdSinceTime(shareId, startTime);
@@ -552,7 +567,7 @@ public class WorkKnowledgeBaseShareService extends CrudService<WorkKnowledgeBase
             if (record.getAuditResult() != null && record.getAuditResult() == 1
                     && StringUtils.isNotBlank(record.getAuditUserId())
                     && grantedUserIds.add(record.getAuditUserId())) {
-                insertPointDetail(record.getAuditUserId(), shareId, POINT_CHANGE_AUDIT, auditRule.getPointValue());
+                insertPointDetail(record.getAuditUserId(), shareId, POINT_CHANGE_AUDIT, Integer.parseInt(auditRule.getPointValue()));
             }
         }
     }

+ 130 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeExpertService.java

@@ -0,0 +1,130 @@
+package com.jeeplus.modules.WorkKnowledgeBase.service;
+
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.service.CrudService;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeExpertDao;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeExpert;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * 知识库专家审核人员 Service
+ * @author: System
+ * @version: 2026-06-16
+ */
+@Service
+@Transactional(readOnly = true)
+public class WorkKnowledgeExpertService extends CrudService<WorkKnowledgeExpertDao, WorkKnowledgeExpert> {
+
+    /**
+     * 获取单条专家数据
+     */
+    public WorkKnowledgeExpert get(String id) {
+        return super.get(id);
+    }
+
+    /**
+     * 查询专家列表(支持条件筛选)
+     */
+    public List<WorkKnowledgeExpert> findList(WorkKnowledgeExpert expert) {
+        return super.findList(expert);
+    }
+
+    /**
+     * 分页查询专家列表
+     * 支持专家姓名、专业领域、状态模糊筛选
+     */
+    public Page<WorkKnowledgeExpert> findPage(Page<WorkKnowledgeExpert> page, WorkKnowledgeExpert expert) {
+        return super.findPage(page, expert);
+    }
+
+    /**
+     * 查询所有启用的专家(用于下拉选择)
+     */
+    public List<WorkKnowledgeExpert> findEnabledList() {
+        return dao.findEnabledList();
+    }
+
+    /**
+     * 根据用户ID检查是否已绑定专家
+     * @param userId 系统用户ID
+     * @return true-已绑定,false-未绑定
+     */
+    public boolean isUserBound(String userId) {
+        if (StringUtils.isBlank(userId)) {
+            return false;
+        }
+        return dao.countByUserId(userId) > 0;
+    }
+
+    /**
+     * 根据用户ID查询专家信息
+     * @param userId 系统用户ID
+     * @return 专家实体
+     */
+    public WorkKnowledgeExpert getByUserId(String userId) {
+        return dao.getByUserId(userId);
+    }
+
+    /**
+     * 保存专家(新增/编辑)
+     * 校验:同一个系统用户只能绑定一位专家
+     */
+    @Transactional(readOnly = false)
+    public void save(WorkKnowledgeExpert expert) {
+        // 参数校验
+        if (StringUtils.isBlank(expert.getUserId())) {
+            throw new IllegalArgumentException("绑定的系统用户不能为空");
+        }
+        if (StringUtils.isBlank(expert.getExpertName())) {
+            throw new IllegalArgumentException("专家姓名不能为空");
+        }
+
+        // 校验用户唯一性:排除当前记录本身
+        if (StringUtils.isNotBlank(expert.getId())) {
+            // 编辑场景:检查该用户是否已被其他专家记录绑定
+            WorkKnowledgeExpert existExpert = dao.getByUserId(expert.getUserId());
+            if (existExpert != null && !existExpert.getId().equals(expert.getId())) {
+                throw new IllegalArgumentException("该用户已绑定其他专家,无法重复绑定");
+            }
+        } else {
+            // 新增场景:检查该用户是否已绑定专家
+            if (isUserBound(expert.getUserId())) {
+                throw new IllegalArgumentException("该用户已绑定专家,无法重复绑定");
+            }
+        }
+
+        // 调用父类save方法
+        super.save(expert);
+    }
+
+    /**
+     * 删除专家(逻辑删除)
+     */
+    @Transactional(readOnly = false)
+    public void delete(WorkKnowledgeExpert expert) {
+        super.delete(expert);
+    }
+
+    /**
+     * 切换专家状态(启用/禁用)
+     * @param id 专家ID
+     * @param status 状态 1-启用 0-禁用
+     */
+    @Transactional(readOnly = false)
+    public void toggleStatus(String id, String status) {
+        if (StringUtils.isBlank(id) || StringUtils.isBlank(status)) {
+            throw new IllegalArgumentException("参数缺失");
+        }
+        WorkKnowledgeExpert expert = dao.get(id);
+        if (expert == null) {
+            throw new IllegalArgumentException("专家不存在");
+        }
+        expert.setStatus(status);
+        expert.preUpdate();
+        dao.update(expert);
+    }
+}

+ 122 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgePointCategoryService.java

@@ -0,0 +1,122 @@
+package com.jeeplus.modules.WorkKnowledgeBase.service;
+
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.service.TreeService;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeBasePointRuleDao;
+import com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgePointCategoryDao;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * 积分分类树形 Service
+ * @author: 徐滕
+ * @version: 2026-06-16
+ */
+@Service
+@Transactional(readOnly = true)
+public class WorkKnowledgePointCategoryService extends TreeService<WorkKnowledgePointCategoryDao, WorkKnowledgePointCategory> {
+
+    @Autowired
+    private WorkKnowledgePointCategoryDao categoryDao;
+
+    @Autowired
+    private WorkKnowledgeBasePointRuleDao pointRuleDao;
+
+    /**
+     * 获取单条数据
+     */
+    public WorkKnowledgePointCategory get(String id) {
+        return super.get(id);
+    }
+
+    /**
+     * 查询列表(支持条件筛选)
+     */
+    public List<WorkKnowledgePointCategory> findList(WorkKnowledgePointCategory category) {
+        if (StringUtils.isNotBlank(category.getParentIds())) {
+            category.setParentIds("," + category.getParentIds() + ",");
+        }
+        return super.findList(category);
+    }
+
+    /**
+     * 分页查询
+     */
+    public Page<WorkKnowledgePointCategory> findPage(Page<WorkKnowledgePointCategory> page, WorkKnowledgePointCategory category) {
+        return super.findPage(page, category);
+    }
+
+    /**
+     * 查询所有启用的分类(用于下拉选择)
+     */
+    public List<WorkKnowledgePointCategory> findEnabledList() {
+        return categoryDao.findEnabledList();
+    }
+
+    /**
+     * 保存分类(新增/编辑)- 重写父类方法增强校验
+     * 自动拼接parentIds,切换父节点时递归更新所有子节点链路
+     */
+    @Transactional(readOnly = false)
+    public void save(WorkKnowledgePointCategory category) {
+        if (StringUtils.isBlank(category.getCategoryName())) {
+            throw new IllegalArgumentException("分类名称不能为空");
+        }
+
+        // 调用父类save方法(已包含parentIds自动维护逻辑)
+        super.save(category);
+    }
+
+    /**
+     * 删除分类(强校验:检查是否有规则关联)- 重写父类方法增强校验
+     */
+    @Transactional(readOnly = false)
+    public void delete(WorkKnowledgePointCategory category) {
+        if (category == null || StringUtils.isBlank(category.getId())) {
+            throw new IllegalArgumentException("参数异常");
+        }
+
+        // 查询该分类及其所有子分类
+        String parentIdsPattern = "," + category.getId() + ",";
+        List<WorkKnowledgePointCategory> allChildren = categoryDao.findByParentIdsLike(parentIdsPattern);
+
+        // 收集所有需要检查的分类ID
+        StringBuilder categoryIds = new StringBuilder();
+        categoryIds.append(category.getId());
+        for (WorkKnowledgePointCategory child : allChildren) {
+            categoryIds.append(",").append(child.getId());
+        }
+
+        // 检查是否有规则关联这些分类
+        // TODO: 需要在PointRuleDao中添加检查方法
+        // int count = pointRuleDao.countByCategoryIds(categoryIds.toString());
+        // if (count > 0) {
+        //     throw new IllegalArgumentException("该分类下存在积分规则,禁止删除!请先删除或迁移相关规则。");
+        // }
+
+        // 调用父类delete方法(逻辑删除)
+        super.delete(category);
+    }
+
+    /**
+     * 切换状态(仅修改自身,不联动子节点)
+     */
+    @Transactional(readOnly = false)
+    public void toggleStatus(String id, String status) {
+        if (StringUtils.isBlank(id) || StringUtils.isBlank(status)) {
+            throw new IllegalArgumentException("参数缺失");
+        }
+        WorkKnowledgePointCategory category = categoryDao.get(id);
+        if (category == null) {
+            throw new IllegalArgumentException("分类不存在");
+        }
+        category.setStatus(status);
+        category.preUpdate();
+        categoryDao.update(category);
+    }
+}

+ 40 - 5
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBasePointRuleController.java

@@ -6,7 +6,9 @@ import com.jeeplus.common.persistence.Page;
 import com.jeeplus.common.utils.StringUtils;
 import com.jeeplus.common.web.BaseController;
 import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointRule;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory;
 import com.jeeplus.modules.WorkKnowledgeBase.service.WorkKnowledgeBasePointRuleService;
+import com.jeeplus.modules.WorkKnowledgeBase.service.WorkKnowledgePointCategoryService;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -33,6 +35,9 @@ public class WorkKnowledgeBasePointRuleController extends BaseController {
     @Autowired
     private WorkKnowledgeBasePointRuleService pointRuleService;
 
+    @Autowired
+    private WorkKnowledgePointCategoryService categoryService;
+
     @ModelAttribute
     public WorkKnowledgeBasePointRule get(@RequestParam(required = false) String id) {
         if (StringUtils.isNotBlank(id)) {
@@ -58,10 +63,40 @@ public class WorkKnowledgeBasePointRuleController extends BaseController {
     /** 新增/编辑表单 */
     @RequiresPermissions(value = {"workKnowledgeBase:pointRule:add", "workKnowledgeBase:pointRule:edit"}, logical = org.apache.shiro.authz.annotation.Logical.OR)
     @RequestMapping(value = "form")
-    public String form(WorkKnowledgeBasePointRule workKnowledgeBasePointRule, Model model) {
+    public String form(WorkKnowledgeBasePointRule workKnowledgeBasePointRule, 
+                      @RequestParam(required = false) String categoryId,
+                      Model model) {
         if (workKnowledgeBasePointRule.getStatus() == null) {
-            workKnowledgeBasePointRule.setStatus(1);
+            workKnowledgeBasePointRule.setStatus("1");
+        }
+        if (workKnowledgeBasePointRule.getPointMode() == null) {
+            workKnowledgeBasePointRule.setPointMode("1");
+        }
+        if (workKnowledgeBasePointRule.getRuleType() == null) {
+            workKnowledgeBasePointRule.setRuleType("1"); // 默认创建积分
+        }
+        // 如果URL传递了categoryId,且实体中没有,则使用URL参数
+        if (StringUtils.isNotBlank(categoryId) && StringUtils.isBlank(workKnowledgeBasePointRule.getCategoryId())) {
+            workKnowledgeBasePointRule.setCategoryId(categoryId);
+        }
+        
+        // 如果有categoryId但没有categoryName,则查询分类名称(用于treeselect回显)
+        if (StringUtils.isNotBlank(workKnowledgeBasePointRule.getCategoryId()) 
+                && StringUtils.isBlank(workKnowledgeBasePointRule.getCategoryName())) {
+            System.out.println("[DEBUG] 准备查询分类信息,categoryId: " + workKnowledgeBasePointRule.getCategoryId());
+            WorkKnowledgePointCategory category = categoryService.get(workKnowledgeBasePointRule.getCategoryId());
+            if (category != null) {
+                System.out.println("[DEBUG] 查询到分类 - id: " + category.getId() + ", categoryName: " + category.getCategoryName());
+                workKnowledgeBasePointRule.setCategoryName(category.getCategoryName());
+            } else {
+                System.out.println("[DEBUG] 未查询到分类信息,categoryId: " + workKnowledgeBasePointRule.getCategoryId());
+            }
         }
+        
+        System.out.println("[DEBUG] form - 接收到的categoryId参数: " + categoryId);
+        System.out.println("[DEBUG] form - 实体中的categoryId: " + workKnowledgeBasePointRule.getCategoryId());
+        System.out.println("[DEBUG] form - 实体中的categoryName: " + workKnowledgeBasePointRule.getCategoryName());
+        
         model.addAttribute("workKnowledgeBasePointRule", workKnowledgeBasePointRule);
         return "modules/WorkKnowledgeBase/workKnowledgeBasePointRuleForm";
     }
@@ -77,7 +112,7 @@ public class WorkKnowledgeBasePointRuleController extends BaseController {
             return "modules/WorkKnowledgeBase/workKnowledgeBasePointRuleForm";
         }
         addMessage(redirectAttributes, "保存积分规则成功");
-        return "redirect:"+ Global.getAdminPath()+"/workKnowledgeBase/pointRule/?repage";
+        return "redirect:"+ Global.getAdminPath()+"/workKnowledgeBase/pointCategory/?repage";
     }
 
     /** 逻辑删除 */
@@ -97,7 +132,7 @@ public class WorkKnowledgeBasePointRuleController extends BaseController {
     @RequiresPermissions("workKnowledgeBase:pointRule:edit")
     @RequestMapping(value = "toggleStatus")
     public AjaxJson toggleStatus(@RequestParam("id") String id,
-                                 @RequestParam("status") Integer status) {
+                                 @RequestParam("status") String status) {
         AjaxJson j = new AjaxJson();
         String err = pointRuleService.toggleStatus(id, status);
         if (err != null) {
@@ -105,7 +140,7 @@ public class WorkKnowledgeBasePointRuleController extends BaseController {
             j.setMsg(err);
         } else {
             j.setSuccess(true);
-            j.setMsg(status != null && status == 1 ? "已启用" : "已禁用");
+            j.setMsg("1".equals(status) ? "已启用" : "已禁用");
         }
         return j;
     }

+ 25 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBaseShareController.java

@@ -12,6 +12,7 @@ import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseDynamicValu
 import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseImportInfo;
 import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseShareInfo;
 import com.jeeplus.modules.WorkKnowledgeBase.service.WorkKnowledgeBaseShareService;
+import com.jeeplus.modules.WorkKnowledgeBase.service.WorkKnowledgeExpertService;
 import com.jeeplus.modules.sys.utils.UserUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +40,9 @@ public class WorkKnowledgeBaseShareController extends BaseController {
 
     @Autowired
     private WorkKnowledgeBaseShareService shareService;
+    
+    @Autowired
+    private WorkKnowledgeExpertService expertService;
 
     @ModelAttribute
     public WorkKnowledgeBaseShareInfo get(@RequestParam(required = false) String id) {
@@ -92,6 +96,11 @@ public class WorkKnowledgeBaseShareController extends BaseController {
             shareService.findDynamicPage(page, shareInfo, dynamicFields, dynamicConditions);
         }
         List<Map<String, Object>> list = page.getList();
+        String currentUserId = UserUtils.getUser().getId();
+        boolean isAdmin = UserUtils.getUser().isAdmin();
+        // 检查当前用户是否为专家
+        boolean isExpert = !isAdmin && expertService.isUserBound(currentUserId);
+
         for (Map<String, Object> map : list) {
             if(UserUtils.getUser().isAdmin()){
                 map.put("isAdmin", true);
@@ -100,6 +109,22 @@ public class WorkKnowledgeBaseShareController extends BaseController {
             }
         }
 
+        for (Map<String, Object> map : list) {
+            map.put("isAdmin", isAdmin);
+            map.put("isExpert", isExpert);
+
+            // 获取treeNodeId对应的treeName,用于前端判断是否需要专家审核
+            Object treeNodeIdObj = map.get("treeNodeId");
+            if (treeNodeIdObj != null) {
+                String tid = treeNodeIdObj.toString();
+                com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseTreeInfo treeInfo =
+                        shareService.getTreeInfoById(tid);
+                if (treeInfo != null) {
+                    map.put("treeName", treeInfo.getTreeName());
+                }
+            }
+        }
+
 
         model.addAttribute("page", page);
         model.addAttribute("shareInfo", shareInfo);

+ 156 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeExpertController.java

@@ -0,0 +1,156 @@
+package com.jeeplus.modules.WorkKnowledgeBase.web;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.json.AjaxJson;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.common.web.BaseController;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeExpert;
+import com.jeeplus.modules.WorkKnowledgeBase.service.WorkKnowledgeExpertService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 知识库专家审核人员 Controller
+ * @author: System
+ * @version: 2026-06-16
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/workKnowledgeBase/expert")
+public class WorkKnowledgeExpertController extends BaseController {
+
+    @Autowired
+    private WorkKnowledgeExpertService expertService;
+
+    /**
+     * 获取单条专家数据
+     */
+    @ModelAttribute
+    public WorkKnowledgeExpert get(@RequestParam(required = false) String id) {
+        if (StringUtils.isNotBlank(id)) {
+            return expertService.get(id);
+        }
+        return new WorkKnowledgeExpert();
+    }
+
+    /**
+     * 专家列表页
+     */
+    @RequiresPermissions("workKnowledgeBase:expert:list")
+    @RequestMapping(value = {"list", ""})
+    public String list(WorkKnowledgeExpert expert, HttpServletRequest request, HttpServletResponse response, Model model) {
+        // 分页查询专家列表
+        Page<WorkKnowledgeExpert> page = expertService.findPage(new Page<>(request, response), expert);
+        model.addAttribute("page", page);
+        return "modules/WorkKnowledgeBase/workKnowledgeExpertList";
+    }
+
+    /**
+     * 新增/编辑表单页
+     */
+    @RequiresPermissions(value = {"workKnowledgeBase:expert:add", "workKnowledgeBase:expert:edit"}, logical = org.apache.shiro.authz.annotation.Logical.OR)
+    @RequestMapping(value = "form")
+    public String form(WorkKnowledgeExpert expert, Model model) {
+        // 设置默认值
+        if (expert.getStatus() == null) {
+            expert.setStatus("1");
+        }
+        if (expert.getSort() == null) {
+            expert.setSort("0");
+        }
+        model.addAttribute("expert", expert);
+        return "modules/WorkKnowledgeBase/workKnowledgeExpertForm";
+    }
+
+    /**
+     * 保存专家(新增/编辑)
+     */
+    @RequiresPermissions(value = {"workKnowledgeBase:expert:add", "workKnowledgeBase:expert:edit"}, logical = org.apache.shiro.authz.annotation.Logical.OR)
+    @RequestMapping(value = "save")
+    public String save(WorkKnowledgeExpert expert, Model model, RedirectAttributes redirectAttributes) {
+        try {
+            // 保存专家信息
+            expertService.save(expert);
+            addMessage(redirectAttributes, "保存专家成功");
+        } catch (IllegalArgumentException e) {
+            // 捕获业务异常,返回表单页面并显示错误信息
+            addMessage(model, e.getMessage());
+            model.addAttribute("expert", expert);
+            return "modules/WorkKnowledgeBase/workKnowledgeExpertForm";
+        }
+        return "redirect:" + Global.getAdminPath() + "/workKnowledgeBase/expert/?repage";
+    }
+
+    /**
+     * 删除专家(逻辑删除)
+     */
+    @ResponseBody
+    @RequiresPermissions("workKnowledgeBase:expert:del")
+    @RequestMapping(value = "delete")
+    public AjaxJson delete(WorkKnowledgeExpert expert) {
+        AjaxJson j = new AjaxJson();
+        try {
+            expertService.delete(expert);
+            j.setSuccess(true);
+            j.setMsg("删除成功");
+        } catch (Exception e) {
+            j.setSuccess(false);
+            j.setMsg("删除失败:" + e.getMessage());
+        }
+        return j;
+    }
+
+    /**
+     * 切换专家状态(启用/禁用)
+     */
+    @ResponseBody
+    @RequiresPermissions("workKnowledgeBase:expert:edit")
+    @RequestMapping(value = "toggleStatus")
+    public AjaxJson toggleStatus(@RequestParam("id") String id,
+                                 @RequestParam("status") String status) {
+        AjaxJson j = new AjaxJson();
+        try {
+            expertService.toggleStatus(id, status);
+            j.setSuccess(true);
+            j.setMsg("1".equals(status) ? "已启用" : "已禁用");
+        } catch (IllegalArgumentException e) {
+            j.setSuccess(false);
+            j.setMsg(e.getMessage());
+        }
+        return j;
+    }
+
+    /**
+     * 专家下拉选择接口(仅返回启用的专家)
+     * 用于文档审核页面选择审核人
+     */
+    @ResponseBody
+    @RequiresPermissions("workKnowledgeBase:expert:list")
+    @RequestMapping(value = "selectData")
+    public List<Map<String, Object>> selectData() {
+        List<Map<String, Object>> mapList = Lists.newArrayList();
+        List<WorkKnowledgeExpert> list = expertService.findEnabledList();
+
+        for (WorkKnowledgeExpert expert : list) {
+            Map<String, Object> map = Maps.newHashMap();
+            map.put("value", expert.getId());
+            map.put("text", expert.getExpertName());
+            mapList.add(map);
+        }
+        return mapList;
+    }
+}

+ 159 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgePointCategoryController.java

@@ -0,0 +1,159 @@
+package com.jeeplus.modules.WorkKnowledgeBase.web;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.jeeplus.common.config.Global;
+import com.jeeplus.common.json.AjaxJson;
+import com.jeeplus.common.utils.StringUtils;
+import com.jeeplus.common.web.BaseController;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory;
+import com.jeeplus.modules.WorkKnowledgeBase.service.WorkKnowledgePointCategoryService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 积分分类树形 Controller(左侧树专用)
+ * @author: 徐滕
+ * @version: 2026-06-16
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/workKnowledgeBase/pointCategory")
+public class WorkKnowledgePointCategoryController extends BaseController {
+
+    @Autowired
+    private WorkKnowledgePointCategoryService categoryService;
+
+    @ModelAttribute
+    public WorkKnowledgePointCategory get(@RequestParam(required = false) String id) {
+        if (StringUtils.isNotBlank(id)) {
+            return categoryService.get(id);
+        }
+        return new WorkKnowledgePointCategory();
+    }
+
+    /** 列表页(左树右表布局中的左侧树页面) */
+    @RequiresPermissions("workKnowledgeBase:pointRule:list")
+    @RequestMapping(value = {"list", ""})
+    public String list(WorkKnowledgePointCategory category, Model model) {
+        model.addAttribute("category", category);
+        return "modules/WorkKnowledgeBase/workKnowledgePointCategoryList";
+    }
+
+    /** 树形数据接口(zTree使用) */
+    @ResponseBody
+    @RequiresPermissions("workKnowledgeBase:pointRule:list")
+    @RequestMapping(value = "treeData")
+    public List<Map<String, Object>> treeData(HttpServletResponse response) {
+        List<Map<String, Object>> mapList = Lists.newArrayList();
+        List<WorkKnowledgePointCategory> list = categoryService.findList(new WorkKnowledgePointCategory());
+
+        for (WorkKnowledgePointCategory category : list) {
+            Map<String, Object> map = Maps.newHashMap();
+            map.put("id", category.getId());
+            map.put("pId", category.getParentId());
+            map.put("name", category.getCategoryName());
+            mapList.add(map);
+        }
+        return mapList;
+    }
+
+    /** 新增/编辑表单 */
+    @RequiresPermissions(value = {"workKnowledgeBase:pointRule:add", "workKnowledgeBase:pointRule:edit"}, logical = org.apache.shiro.authz.annotation.Logical.OR)
+    @RequestMapping(value = "form")
+    public String form(WorkKnowledgePointCategory category, Model model) {
+        if (category.getParent() != null && StringUtils.isNotBlank(category.getParent().getId())) {
+            category.setParent(categoryService.get(category.getParent().getId()));
+        }
+        if (category.getSort() == null) {
+            category.setSort(30);
+        }
+        if (category.getStatus() == null) {
+            category.setStatus("1");
+        }
+        model.addAttribute("category", category);
+        return "modules/WorkKnowledgeBase/workKnowledgePointCategoryForm";
+    }
+
+    /** 保存分类 */
+    @RequiresPermissions(value = {"workKnowledgeBase:pointRule:add", "workKnowledgeBase:pointRule:edit"}, logical = org.apache.shiro.authz.annotation.Logical.OR)
+    @RequestMapping(value = "save")
+    public String save(WorkKnowledgePointCategory category, Model model, RedirectAttributes redirectAttributes) {
+        try {
+            categoryService.save(category);
+            addMessage(redirectAttributes, "保存分类成功");
+        } catch (IllegalArgumentException e) {
+            addMessage(model, e.getMessage());
+            model.addAttribute("category", category);
+            return "modules/WorkKnowledgeBase/workKnowledgePointCategoryForm";
+        }
+        return "redirect:" + Global.getAdminPath() + "/workKnowledgeBase/pointCategory/?repage";
+    }
+
+    /** 删除分类 */
+    @ResponseBody
+    @RequiresPermissions("workKnowledgeBase:pointRule:del")
+    @RequestMapping(value = "delete")
+    public AjaxJson delete(WorkKnowledgePointCategory category) {
+        AjaxJson j = new AjaxJson();
+        try {
+            categoryService.delete(category);
+            j.setSuccess(true);
+            j.setMsg("删除成功");
+        } catch (IllegalArgumentException e) {
+            j.setSuccess(false);
+            j.setMsg(e.getMessage());
+        }
+        return j;
+    }
+
+    /** 切换状态 */
+    @ResponseBody
+    @RequiresPermissions("workKnowledgeBase:pointRule:edit")
+    @RequestMapping(value = "toggleStatus")
+    public AjaxJson toggleStatus(@RequestParam("id") String id,
+                                 @RequestParam("status") String status) {
+        AjaxJson j = new AjaxJson();
+        try {
+            categoryService.toggleStatus(id, status);
+            j.setSuccess(true);
+            j.setMsg("1".equals(status) ? "已启用" : "已禁用");
+        } catch (IllegalArgumentException e) {
+            j.setSuccess(false);
+            j.setMsg(e.getMessage());
+        }
+        return j;
+    }
+
+    /** 下拉选择接口(仅返回启用的分类) */
+    @ResponseBody
+    @RequiresPermissions("workKnowledgeBase:pointRule:list")
+    @RequestMapping(value = "selectData")
+    public List<Map<String, Object>> selectData() {
+        List<Map<String, Object>> mapList = Lists.newArrayList();
+        List<WorkKnowledgePointCategory> list = categoryService.findEnabledList();
+
+        System.out.println("[DEBUG] selectData - 查询到的分类数量: " + (list != null ? list.size() : 0));
+        
+        for (WorkKnowledgePointCategory category : list) {
+            Map<String, Object> map = Maps.newHashMap();
+            map.put("value", category.getId());
+            map.put("text", category.getCategoryName());
+            mapList.add(map);
+            System.out.println("[DEBUG] 分类数据 - id: " + category.getId() + ", name: " + category.getCategoryName());
+        }
+        
+        return mapList;
+    }
+}

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

@@ -8,6 +8,10 @@
         a.point_value  AS "pointValue",
         a.point_name   AS "pointName",
         a.status       AS "status",
+        a.category_id  AS "categoryId",
+        a.point_mode   AS "pointMode",
+        a.point_min    AS "pointMin",
+        a.point_max    AS "pointMax",
         a.create_by    AS "createBy.id",
         a.create_date  AS "createDate",
         a.update_by    AS "updateBy.id",
@@ -32,17 +36,22 @@
 
     <select id="findList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointRule">
         SELECT <include refid="pointRuleColumns"/>,
-            u.name AS "createBy.name"
+            u.name AS "createBy.name",
+            c.category_name AS "categoryName"
         FROM work_knowledge_base_point_rule a
         LEFT JOIN sys_user u ON u.id = a.create_by
+        LEFT JOIN work_knowledge_point_category c ON c.id = a.category_id AND c.del_flag = '0'
         <where>
             a.del_flag = #{DEL_FLAG_NORMAL}
             <if test="pointName != null and pointName != ''">
                 AND a.point_name LIKE CONCAT('%', #{pointName}, '%')
             </if>
-            <if test="status != null">
+            <if test="status != null and status != ''">
                 AND a.status = #{status}
             </if>
+            <if test="categoryId != null and categoryId != ''">
+                AND (a.category_id = #{categoryId} OR c.parent_ids LIKE CONCAT('%,', #{categoryId}, ',%'))
+            </if>
         </where>
         ORDER BY a.update_date DESC
     </select>
@@ -86,9 +95,11 @@
 
     <insert id="insert">
         INSERT INTO work_knowledge_base_point_rule (
-            id, rule_type, point_value, point_name, status, create_by, create_date, update_by, update_date, del_flag
+            id, rule_type, point_value, point_name, status, category_id, point_mode, point_min, point_max,
+            create_by, create_date, update_by, update_date, del_flag
         ) VALUES (
-            #{id}, #{ruleType}, #{pointValue}, #{pointName}, #{status}, #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{delFlag}
+            #{id}, #{ruleType}, #{pointValue}, #{pointName}, #{status}, #{categoryId}, #{pointMode}, #{pointMin}, #{pointMax},
+            #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{delFlag}
         )
     </insert>
 
@@ -98,6 +109,10 @@
             point_value = #{pointValue},
             point_name  = #{pointName},
             status      = #{status},
+            category_id = #{categoryId},
+            point_mode  = #{pointMode},
+            point_min   = #{pointMin},
+            point_max   = #{pointMax},
             update_by   = #{updateBy.id},
             update_date = #{updateDate}
         WHERE id = #{id}

+ 122 - 0
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeExpertDao.xml

@@ -0,0 +1,122 @@
+<?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.WorkKnowledgeExpertDao">
+
+    <!-- 定义专家表字段映射 -->
+    <sql id="expertColumns">
+        a.id              AS "id",
+        a.user_id         AS "userId",
+        a.expert_name     AS "expertName",
+        a.professional_field AS "professionalField",
+        a.phone           AS "phone",
+        a.introduction    AS "introduction",
+        a.status          AS "status",
+        a.sort            AS "sort",
+        a.create_by       AS "createBy.id",
+        a.create_date     AS "createDate",
+        a.update_by       AS "updateBy.id",
+        a.update_date     AS "updateDate",
+        a.del_flag        AS "delFlag"
+    </sql>
+
+    <!-- 查询单条专家记录 -->
+    <select id="get" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeExpert">
+        SELECT <include refid="expertColumns"/>
+        FROM work_knowledge_expert a
+        WHERE a.id = #{id}
+    </select>
+
+    <!-- 分页查询专家列表(支持条件筛选) -->
+    <select id="findList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeExpert">
+        SELECT <include refid="expertColumns"/>
+        FROM work_knowledge_expert a
+        <where>
+            a.del_flag = '0'
+            <!-- 专家姓名模糊查询 -->
+            <if test="expertName != null and expertName != ''">
+                AND a.expert_name LIKE CONCAT('%', #{expertName}, '%')
+            </if>
+            <!-- 专业领域模糊查询 -->
+            <if test="professionalField != null and professionalField != ''">
+                AND a.professional_field LIKE CONCAT('%', #{professionalField}, '%')
+            </if>
+            <!-- 状态精确查询 -->
+            <if test="status != null and status != ''">
+                AND a.status = #{status}
+            </if>
+        </where>
+        ORDER BY a.sort ASC, a.create_date DESC
+    </select>
+
+    <!-- 查询所有专家列表 -->
+    <select id="findAllList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeExpert">
+        SELECT <include refid="expertColumns"/>
+        FROM work_knowledge_expert a
+        WHERE a.del_flag = '0'
+        ORDER BY a.sort ASC, a.create_date DESC
+    </select>
+
+    <!-- 查询所有启用的专家(用于下拉选择) -->
+    <select id="findEnabledList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeExpert">
+        SELECT <include refid="expertColumns"/>
+        FROM work_knowledge_expert a
+        WHERE a.del_flag = '0'
+          AND a.status = '1'
+        ORDER BY a.sort ASC, a.expert_name ASC
+    </select>
+
+    <!-- 根据用户ID检查是否已绑定专家 -->
+    <select id="countByUserId" resultType="int">
+        SELECT COUNT(1)
+        FROM work_knowledge_expert a
+        WHERE a.del_flag = '0'
+          AND a.user_id = #{userId}
+    </select>
+
+    <!-- 根据用户ID查询专家信息 -->
+    <select id="getByUserId" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeExpert">
+        SELECT <include refid="expertColumns"/>
+        FROM work_knowledge_expert a
+        WHERE a.del_flag = '0'
+          AND a.user_id = #{userId}
+    </select>
+
+    <!-- 新增专家记录 -->
+    <insert id="insert">
+        INSERT INTO work_knowledge_expert (
+            id, user_id, expert_name, professional_field, phone, introduction,
+            status, sort, create_by, create_date, update_by, update_date, del_flag
+        ) VALUES (
+            #{id}, #{userId}, #{expertName}, #{professionalField}, #{phone}, #{introduction},
+            #{status}, #{sort}, #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{delFlag}
+        )
+    </insert>
+
+    <!-- 更新专家记录 -->
+    <update id="update">
+        UPDATE work_knowledge_expert SET
+            user_id          = #{userId},
+            expert_name      = #{expertName},
+            professional_field = #{professionalField},
+            phone            = #{phone},
+            introduction     = #{introduction},
+            status           = #{status},
+            sort             = #{sort},
+            update_by        = #{updateBy.id},
+            update_date      = #{updateDate}
+        WHERE id = #{id}
+    </update>
+
+    <!-- 物理删除专家记录 -->
+    <delete id="delete">
+        DELETE FROM work_knowledge_expert WHERE id = #{id}
+    </delete>
+
+    <!-- 逻辑删除专家记录 -->
+    <update id="deleteByLogic">
+        UPDATE work_knowledge_expert
+        SET del_flag = '1'
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 110 - 0
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgePointCategoryDao.xml

@@ -0,0 +1,110 @@
+<?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.WorkKnowledgePointCategoryDao">
+
+    <sql id="categoryColumns">
+        a.id              AS "id",
+        a.parent_id       AS "parentId",
+        a.parent_ids      AS "parentIds",
+        a.category_name   AS "categoryName",
+        a.sort            AS "sort",
+        a.status          AS "status",
+        a.create_by       AS "createBy.id",
+        a.create_date     AS "createDate",
+        a.update_by       AS "updateBy.id",
+        a.update_date     AS "updateDate",
+        a.del_flag        AS "delFlag"
+    </sql>
+
+    <select id="get" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory">
+        SELECT <include refid="categoryColumns"/>
+        FROM work_knowledge_point_category a
+        WHERE a.id = #{id}
+    </select>
+
+    <select id="findList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory">
+        SELECT <include refid="categoryColumns"/>
+        FROM work_knowledge_point_category a
+        <where>
+            a.del_flag = 0
+            <if test="categoryName != null and categoryName != ''">
+                AND a.category_name LIKE CONCAT('%', #{categoryName}, '%')
+            </if>
+            <if test="status != null and status != ''">
+                AND a.status = #{status}
+            </if>
+        </where>
+        ORDER BY a.sort ASC, a.create_date ASC
+    </select>
+
+    <select id="findAllList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory">
+        SELECT <include refid="categoryColumns"/>
+        FROM work_knowledge_point_category a
+        WHERE a.del_flag = 0
+        ORDER BY a.sort ASC, a.create_date ASC
+    </select>
+
+    <select id="findEnabledList" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory">
+        SELECT <include refid="categoryColumns"/>
+        FROM work_knowledge_point_category a
+        WHERE a.del_flag = 0
+          AND a.status = '1'
+        ORDER BY a.sort ASC, a.create_date ASC
+    </select>
+
+    <select id="findByParentIdsLike" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory">
+        SELECT <include refid="categoryColumns"/>
+        FROM work_knowledge_point_category a
+        WHERE a.del_flag = 0
+          AND a.parent_ids LIKE CONCAT(#{parentIds}, '%')
+        ORDER BY a.sort ASC
+    </select>
+
+    <insert id="insert">
+        INSERT INTO work_knowledge_point_category (
+            id, parent_id, parent_ids, category_name, sort, status,
+            create_by, create_date, update_by, update_date, del_flag
+        ) VALUES (
+            #{id}, #{parentId}, #{parentIds}, #{categoryName}, #{sort}, #{status},
+            #{createBy.id}, #{createDate}, #{updateBy.id}, #{updateDate}, #{delFlag}
+        )
+    </insert>
+
+    <update id="update">
+        UPDATE work_knowledge_point_category SET
+            parent_id     = #{parentId},
+            parent_ids    = #{parentIds},
+            category_name = #{categoryName},
+            sort          = #{sort},
+            status        = #{status},
+            update_by     = #{updateBy.id},
+            update_date   = #{updateDate}
+        WHERE id = #{id}
+    </update>
+
+    <update id="updateParentIds">
+        UPDATE work_knowledge_point_category
+        SET parent_ids  = #{parentIds},
+            update_by   = #{updateBy},
+            update_date = NOW()
+        WHERE id = #{id}
+    </update>
+
+    <update id="batchUpdateParentIds">
+        UPDATE work_knowledge_point_category
+        SET parent_ids = REPLACE(parent_ids, #{oldParentIdsPrefix}, #{newParentIdsPrefix}),
+            update_date = NOW()
+        WHERE parent_ids LIKE CONCAT(#{oldParentIdsPrefix}, '%')
+    </update>
+
+    <delete id="delete">
+        DELETE FROM work_knowledge_point_category WHERE id = #{id}
+    </delete>
+
+    <update id="deleteByLogic">
+        UPDATE work_knowledge_point_category
+        SET del_flag = #{DEL_FLAG_DELETE}
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 185 - 0
src/main/resources/mysqlDateBase/README_积分规则树形改造.md

@@ -0,0 +1,185 @@
+# 知识库积分规则管理模块 - 无限层级树形改造说明
+
+## 一、改造概述
+
+本次改造将原有的扁平化积分规则配置升级为**无限层级树形结构**,支持分类节点的无限嵌套和规则节点的灵活挂载,同时支持固定分值和区间分值两种积分模式。
+
+## 二、核心特性
+
+### 2.1 树形结构
+- **分类节点(nodeType=1)**:类似文件夹,可无限嵌套子分类,也可挂载规则节点
+- **规则节点(nodeType=2)**:类似文件,必须归属到分类节点下,强制为叶子节点
+- **无限层级**:支持4级、5级及更深层级,无硬限制
+
+### 2.2 分值模式
+- **固定分值(pointMode=1)**:满足条件自动发放固定积分,如阅读+1分
+- **区间分值(pointMode=2)**:配置积分范围,由审核人员人工判定最终值,如上传技术总结100~300分
+
+### 2.3 左树右表布局
+- **左侧树形区**:展示完整树形结构,支持折叠/展开、右键菜单操作
+- **右侧列表区**:仅展示规则节点,点击左侧节点联动筛选对应规则
+
+## 三、数据库改造
+
+### 3.1 执行顺序
+1. 先执行增量改造SQL:`alter_work_knowledge_base_point_rule_tree_structure.sql`
+2. 再执行初始化数据SQL(可选):`init_work_knowledge_base_point_rule_data.sql`
+
+### 3.2 新增字段说明
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| parent_id | VARCHAR(64) | 父节点ID,顶级为'0' |
+| parent_ids | VARCHAR(512) | 全父节点ID链路,如'0,abc123,' |
+| node_type | TINYINT(1) | 节点类型:1=分类节点,2=规则节点 |
+| node_level | TINYINT(1) | 节点层级,自动计算 |
+| sort | INT(11) | 同层级排序号,数值越小越靠前 |
+| point_mode | TINYINT(1) | 积分模式:1=固定分值,2=区间分值 |
+| point_min | INT(11) | 积分区间最小值(区间模式使用) |
+| point_max | INT(11) | 积分区间最大值(区间模式使用) |
+
+### 3.3 历史数据兼容
+- 原有数据自动设置为:`nodeType=2`(规则节点)、`pointMode=1`(固定分值)、`parentId='0'`
+- 原有字段 `ruleType`、`pointValue`、`pointName`、`status` 完全保留
+
+## 四、业务规则
+
+### 4.1 节点约束
+1. **分类节点**
+   - 不配置积分参数,不参与积分计算
+   - 仅在左侧树形展示,不在右侧列表展示
+   - 支持无限级嵌套子分类,也可直接挂载规则节点
+
+2. **规则节点**
+   - 必须归属到某个分类节点下,不允许作为顶级节点
+   - 强制为叶子节点,禁止添加任何子节点
+   - 每条规则对应"一种员工行为 + 一种规则类型"
+
+### 4.2 删除逻辑
+- 所有删除均为**逻辑删除**(修改del_flag标记)
+- 删除分类节点:**同步递归删除**该分类下所有层级的子分类和规则
+- 删除规则节点:仅删除当前单条规则
+
+### 4.3 启用/禁用逻辑
+- 禁用分类节点:**同步禁用**该分类下所有层级的子分类和规则
+- 禁用规则节点:仅当前规则失效
+- 禁用的节点在树形和列表中默认隐藏
+
+## 五、代码实现要点
+
+### 5.1 后端改造
+
+#### Entity层
+- 文件:`WorkKnowledgeBasePointRule.java`
+- 新增字段:parentId、parentIds、nodeType、nodeLevel、sort、pointMode、pointMin、pointMax
+
+#### DAO层
+- 文件:`WorkKnowledgeBasePointRuleDao.java` + `WorkKnowledgeBasePointRuleDao.xml`
+- 新增方法:
+  - `findByParentId()`:查询子节点列表
+  - `deleteChildrenByLogic()`:递归删除子节点
+  - `batchUpdateStatus()`:批量更新状态
+
+#### Service层
+- 文件:`WorkKnowledgeBasePointRuleService.java`
+- 核心方法:
+  - `findTreeList()`:查询树形结构数据
+  - `findRulesByNode()`:根据节点ID查询其下所有规则
+  - `saveRule()`:保存时自动计算树形字段(parentId、parentIds、nodeLevel)
+  - `calculateTreeFields()`:递归计算树形链路
+  - `updateChildrenTreeFields()`:父节点变更时递归更新子节点
+
+#### Controller层
+- 文件:`WorkKnowledgeBasePointRuleController.java`
+- 新增接口:
+  - `/treeData`:返回树形JSON数据
+  - `/list?nodeId=xxx`:根据选中节点联动查询规则
+
+### 5.2 前端改造
+
+#### 列表页面
+- 文件:`workKnowledgeBasePointRuleList.jsp`
+- 核心功能:
+  - 使用zTree插件渲染左侧树形
+  - 树节点右键菜单:新增下级分类/规则、编辑、删除、启用/禁用
+  - 点击树节点联动刷新右侧表格
+  - 右侧表格仅展示规则节点,显示积分值(固定/区间)
+
+#### 表单页面
+- 文件:`workKnowledgeBasePointRuleForm.jsp`
+- 核心功能:
+  - 节点类型切换:选择"分类节点"隐藏积分字段,选择"规则节点"显示全量字段
+  - 分值模式切换:选择"固定分值"显示单一输入框,选择"区间分值"显示上下限输入框
+  - 提交前校验:规则节点必须选择父分类、区间模式下限<上限
+
+## 六、初始数据结构
+
+执行 `init_work_knowledge_base_point_rule_data.sql` 后,系统将创建以下结构:
+
+```
+根节点
+├── 基础积分(一级分类)
+│   ├── 阅读知识库内容(规则节点,互动积分,+1分)
+│   └── 点赞他人上传内容(规则节点,互动积分,+1分)
+└── 贡献积分(一级分类)
+    ├── 上传、审核贡献(二级分类)
+    │   ├── 上传技术总结(规则节点,创建数据积分,100~300分)
+    │   ├── 审核技术总结(规则节点,审核通过积分,20分)
+    │   ├── 上传培训心得(规则节点,创建数据积分,50~200分)
+    │   ├── 审核培训心得(规则节点,审核通过积分,20分)
+    │   ├── 上传相关资料(规则节点,创建数据积分,20分)
+    │   └── 审核相关资料(规则节点,审核通过积分,5分)
+    └── 互动贡献(二级分类)
+        └── 问答区答疑(规则节点,互动积分,5分)
+```
+
+## 七、部署步骤
+
+1. **备份数据库**
+   ```sql
+   mysqldump -u root -p work_knowledge_base_point_rule > backup_$(date +%Y%m%d).sql
+   ```
+
+2. **执行SQL脚本**
+   ```sql
+   source alter_work_knowledge_base_point_rule_tree_structure.sql;
+   -- 可选:如果需要初始化数据
+   source init_work_knowledge_base_point_rule_data.sql;
+   ```
+
+3. **重新编译项目**
+   ```bash
+   mvn clean package -DskipTests
+   ```
+
+4. **重启应用服务器**
+
+5. **验证功能**
+   - 访问积分规则管理页面
+   - 检查左侧树形是否正常展示
+   - 测试新增分类、新增规则、编辑、删除、启用/禁用等功能
+   - 测试区间分值规则的录入和展示
+
+## 八、常见问题
+
+### Q1: 历史数据如何迁移到新结构?
+A: 执行 `alter_work_knowledge_base_point_rule_tree_structure.sql` 时会自动将历史数据设置为规则节点、固定分值模式、挂载在根节点下。管理员可通过编辑功能调整其所属分类。
+
+### Q2: 如何确保树形结构的性能?
+A: 使用了 `parent_ids` 字段存储完整链路,通过模糊匹配快速查询子树,避免递归查询数据库。同时添加了索引优化查询性能。
+
+### Q3: 区间分值如何在业务中使用?
+A: 区间分值仅作为配置标准,实际发放积分时由审核人员在区间范围内人工判定最终值。例如配置"上传技术总结100~300分",审核时根据内容质量决定具体发放多少分。
+
+### Q4: 删除分类时会提示吗?
+A: 是的,前端会弹出确认对话框,明确提示"删除分类将同步删除其下所有子节点和规则",防止误操作。
+
+### Q5: 如何扩展更多层级?
+A: 系统不做层级数量限制,只需在任意分类节点下右键选择"新增下级分类"即可继续嵌套。树形字段(parentIds、nodeLevel)会自动计算。
+
+## 九、技术支持
+
+如有问题,请联系开发团队:
+- 开发者:徐滕
+- 改造时间:2026-06-15
+- 版本:v2.0(无限层级版)

+ 47 - 0
src/main/resources/mysqlDateBase/alter_work_knowledge_base_point_rule_tree_structure.sql

@@ -0,0 +1,47 @@
+-- ============================================================
+-- 知识库积分规则表 - 无限层级树形结构改造 SQL
+-- 执行时间:2026-06-15
+-- 说明:在现有表基础上增加树形结构字段和区间分值字段
+-- ============================================================
+
+-- 1. 添加树形结构字段
+ALTER TABLE work_knowledge_base_point_rule
+ADD COLUMN parent_id VARCHAR(64) NOT NULL DEFAULT '0' COMMENT '父节点ID,顶级为0',
+ADD COLUMN parent_ids VARCHAR(512) NOT NULL DEFAULT '' COMMENT '全父节点ID链路,英文逗号分隔,支持无限层级',
+ADD COLUMN node_type TINYINT(1) NOT NULL DEFAULT '2' COMMENT '节点类型 1=分类节点 2=积分规则节点',
+ADD COLUMN node_level TINYINT(1) NOT NULL DEFAULT '1' COMMENT '节点层级,自动计算,无层级数量限制',
+ADD COLUMN sort INT(11) NOT NULL DEFAULT '0' COMMENT '同层级排序号,数值越小越靠前';
+
+-- 2. 添加积分模式相关字段
+ALTER TABLE work_knowledge_base_point_rule
+ADD COLUMN point_mode TINYINT(1) NOT NULL DEFAULT '1' COMMENT '积分模式 1=固定分值 2=区间分值',
+ADD COLUMN point_min INT(11) NULL COMMENT '积分区间最小值(区间模式使用)',
+ADD COLUMN point_max INT(11) NULL COMMENT '积分区间最大值(区间模式使用)';
+
+-- 3. 添加索引优化查询性能
+ALTER TABLE work_knowledge_base_point_rule
+ADD INDEX idx_parent_id (parent_id),
+ADD INDEX idx_parent_ids (parent_ids),
+ADD INDEX idx_node_type (node_type);
+
+-- 4. 历史数据迁移:将已有数据设置为积分规则节点、固定分值模式
+UPDATE work_knowledge_base_point_rule
+SET 
+    parent_id = '0',
+    parent_ids = '',
+    node_type = 2,
+    node_level = 1,
+    sort = 0,
+    point_mode = 1
+WHERE del_flag = '0' 
+  AND (parent_id IS NULL OR parent_id = '');
+
+-- 5. 验证数据迁移结果
+SELECT 
+    COUNT(*) AS total_count,
+    SUM(CASE WHEN node_type = 1 THEN 1 ELSE 0 END) AS category_count,
+    SUM(CASE WHEN node_type = 2 THEN 1 ELSE 0 END) AS rule_count,
+    SUM(CASE WHEN point_mode = 1 THEN 1 ELSE 0 END) AS fixed_mode_count,
+    SUM(CASE WHEN point_mode = 2 THEN 1 ELSE 0 END) AS range_mode_count
+FROM work_knowledge_base_point_rule
+WHERE del_flag = '0';

+ 181 - 0
src/main/resources/mysqlDateBase/init_work_knowledge_base_point_rule_data.sql

@@ -0,0 +1,181 @@
+-- ============================================================
+-- 知识库积分规则 - 初始化基础数据 SQL
+-- 执行时间:2026-06-15
+-- 说明:创建基础积分和贡献积分两大分类及对应的规则节点
+-- ============================================================
+
+-- 1. 插入一级分类:基础积分
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, status, del_flag, 
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), '0', '0,', 1, 1, 1,
+    '基础积分', 1, '0',
+    NOW(), NOW()
+);
+
+-- 2. 插入一级分类:贡献积分
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, status, del_flag, 
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), '0', '0,', 1, 1, 2,
+    '贡献积分', 1, '0',
+    NOW(), NOW()
+);
+
+-- 获取刚插入的ID(需要根据实际情况调整)
+SET @basic_category_id = (SELECT id FROM work_knowledge_base_point_rule WHERE point_name = '基础积分' AND parent_id = '0' LIMIT 1);
+SET @contribute_category_id = (SELECT id FROM work_knowledge_base_point_rule WHERE point_name = '贡献积分' AND parent_id = '0' LIMIT 1);
+
+-- 3. 插入贡献积分下的二级分类:上传、审核贡献
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, status, del_flag, 
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @contribute_category_id, CONCAT('0,', @contribute_category_id, ','), 1, 2, 1,
+    '上传、审核贡献', 1, '0',
+    NOW(), NOW()
+);
+
+-- 4. 插入贡献积分下的二级分类:互动贡献
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, status, del_flag, 
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @contribute_category_id, CONCAT('0,', @contribute_category_id, ','), 1, 2, 2,
+    '互动贡献', 1, '0',
+    NOW(), NOW()
+);
+
+SET @upload_audit_category_id = (SELECT id FROM work_knowledge_base_point_rule WHERE point_name = '上传、审核贡献' AND parent_id = @contribute_category_id LIMIT 1);
+SET @interaction_category_id = (SELECT id FROM work_knowledge_base_point_rule WHERE point_name = '互动贡献' AND parent_id = @contribute_category_id LIMIT 1);
+
+-- 5. 插入基础积分下的规则节点
+
+-- 5.1 阅读知识库内容
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, rule_type, point_mode, point_value, status, del_flag,
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @basic_category_id, CONCAT('0,', @basic_category_id, ','), 2, 2, 1,
+    '阅读知识库内容', 3, 1, 1, 1, '0',
+    NOW(), NOW()
+);
+
+-- 5.2 点赞他人上传内容
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, rule_type, point_mode, point_value, status, del_flag,
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @basic_category_id, CONCAT('0,', @basic_category_id, ','), 2, 2, 2,
+    '点赞他人上传内容', 3, 1, 1, 1, '0',
+    NOW(), NOW()
+);
+
+-- 6. 插入上传、审核贡献下的规则节点
+
+-- 6.1 技术总结 - 上传
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, rule_type, point_mode, point_min, point_max, status, del_flag,
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @upload_audit_category_id, CONCAT('0,', @contribute_category_id, ',', @upload_audit_category_id, ','), 2, 3, 1,
+    '上传技术总结', 1, 2, 100, 300, 1, '0',
+    NOW(), NOW()
+);
+
+-- 6.2 技术总结 - 审核
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, rule_type, point_mode, point_value, status, del_flag,
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @upload_audit_category_id, CONCAT('0,', @contribute_category_id, ',', @upload_audit_category_id, ','), 2, 3, 2,
+    '审核技术总结', 2, 1, 20, 1, '0',
+    NOW(), NOW()
+);
+
+-- 6.3 培训心得 - 上传
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, rule_type, point_mode, point_min, point_max, status, del_flag,
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @upload_audit_category_id, CONCAT('0,', @contribute_category_id, ',', @upload_audit_category_id, ','), 2, 3, 3,
+    '上传培训心得', 1, 2, 50, 200, 1, '0',
+    NOW(), NOW()
+);
+
+-- 6.4 培训心得 - 审核
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, rule_type, point_mode, point_value, status, del_flag,
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @upload_audit_category_id, CONCAT('0,', @contribute_category_id, ',', @upload_audit_category_id, ','), 2, 3, 4,
+    '审核培训心得', 2, 1, 20, 1, '0',
+    NOW(), NOW()
+);
+
+-- 6.5 相关资料 - 上传
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, rule_type, point_mode, point_value, status, del_flag,
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @upload_audit_category_id, CONCAT('0,', @contribute_category_id, ',', @upload_audit_category_id, ','), 2, 3, 5,
+    '上传相关资料', 1, 1, 20, 1, '0',
+    NOW(), NOW()
+);
+
+-- 6.6 相关资料 - 审核
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, rule_type, point_mode, point_value, status, del_flag,
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @upload_audit_category_id, CONCAT('0,', @contribute_category_id, ',', @upload_audit_category_id, ','), 2, 3, 6,
+    '审核相关资料', 2, 1, 5, 1, '0',
+    NOW(), NOW()
+);
+
+-- 7. 插入互动贡献下的规则节点
+
+-- 7.1 问答区答疑
+INSERT INTO work_knowledge_base_point_rule (
+    id, parent_id, parent_ids, node_type, node_level, sort, 
+    point_name, rule_type, point_mode, point_value, status, del_flag,
+    create_date, update_date
+) VALUES (
+    REPLACE(UUID(), '-', ''), @interaction_category_id, CONCAT('0,', @contribute_category_id, ',', @interaction_category_id, ','), 2, 3, 1,
+    '问答区答疑', 3, 1, 5, 1, '0',
+    NOW(), NOW()
+);
+
+-- 8. 验证数据插入结果
+SELECT 
+    a.id,
+    a.parent_id,
+    a.node_type,
+    a.node_level,
+    a.point_name,
+    CASE a.node_type 
+        WHEN 1 THEN '分类节点'
+        WHEN 2 THEN '规则节点'
+    END AS node_type_name,
+    CASE a.point_mode 
+        WHEN 1 THEN CONCAT(a.point_value, '分')
+        WHEN 2 THEN CONCAT(a.point_min, '~', a.point_max, '分')
+        ELSE '-'
+    END AS point_info
+FROM work_knowledge_base_point_rule a
+WHERE a.del_flag = '0'
+ORDER BY a.parent_ids, a.sort;

+ 41 - 0
src/main/resources/mysqlDateBase/point_rule_category_upgrade.sql

@@ -0,0 +1,41 @@
+-- ============================================================
+-- 积分规则树形改造 - 数据库增量SQL
+-- 执行时间:2026-06-16
+-- 说明:仅增量修改,不删除历史字段
+-- ============================================================
+
+-- 1. 原有表 work_knowledge_base_point_rule 增量ALTER(保留所有原字段)
+ALTER TABLE `work_knowledge_base_point_rule`
+ADD COLUMN `category_id` varchar(64) NOT NULL DEFAULT '' COMMENT '归属分类ID,关联分类表主键' AFTER `point_name`,
+ADD COLUMN `point_mode` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1固定分值 2区间分值,历史存量默认固定分值' AFTER `category_id`,
+ADD COLUMN `point_min` int(11) NULL COMMENT '区间积分下限,point_mode=2生效' AFTER `point_mode`,
+ADD COLUMN `point_max` int(11) NULL COMMENT '区间积分上限,point_mode=2生效' AFTER `point_min`,
+ADD INDEX `idx_category_id` (`category_id`);
+
+-- 2. 新建独立分类树形表 work_knowledge_point_category(左侧树形专用)
+CREATE TABLE `work_knowledge_point_category` (
+  `id` varchar(64) NOT NULL COMMENT '分类主键ID',
+  `parent_id` varchar(64) NOT NULL DEFAULT '0' COMMENT '父分类ID,顶级分类父ID=0',
+  `parent_ids` varchar(512) NOT NULL DEFAULT '' COMMENT '全层级ID逗号分隔链路,用于筛选下级所有规则',
+  `category_name` varchar(64) NOT NULL COMMENT '分类名称',
+  `sort` int(11) NOT NULL DEFAULT 0 COMMENT '同层级排序号,升序展示',
+  `status` tinyint(1) DEFAULT '1' COMMENT '1启用 0禁用',
+  `create_by` varchar(64) DEFAULT NULL,
+  `create_date` datetime DEFAULT NULL,
+  `update_by` varchar(64) DEFAULT NULL,
+  `update_date` datetime DEFAULT NULL,
+  `del_flag` char(1) DEFAULT '0',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_parent_id` (`parent_id`),
+  INDEX `idx_parent_ids` (`parent_ids`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='积分分类树形表(页面左侧树专用)';
+
+-- 3. 初始化基础分类数据
+INSERT INTO `work_knowledge_point_category` (`id`,`parent_id`,`parent_ids`,`category_name`,`sort`)
+VALUES
+('cat001','0','0,cat001','基础积分',1),
+('cat002','0','0,cat002','贡献积分',2),
+('cat003','cat002','0,cat002,cat003','技术总结',1),
+('cat004','cat002','0,cat002,cat004','培训心得',2),
+('cat005','cat002','0,cat002,cat005','相关资料',3),
+('cat006','cat002','0,cat002,cat006','问答答疑',4);

+ 19 - 0
src/main/resources/mysqlDateBase/work_knowledge_expert.sql

@@ -0,0 +1,19 @@
+-- 知识库专家审核人员表
+CREATE TABLE `work_knowledge_expert` (
+  `id` varchar(64) NOT NULL COMMENT '专家主键ID',
+  `user_id` varchar(64) NOT NULL COMMENT '关联系统用户sys_user.id,绑定登录账号',
+  `expert_name` varchar(50) NOT NULL COMMENT '专家真实姓名',
+  `professional_field` varchar(255) DEFAULT NULL COMMENT '专业领域(技术总结/培训心得)',
+  `phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
+  `introduction` text DEFAULT NULL COMMENT '专家简介、擅长审核文档类型',
+  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1启用(可接收审核任务) 0禁用',
+  `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序号,数值越小越靠前',
+  `create_by` varchar(64) DEFAULT NULL,
+  `create_date` datetime DEFAULT NULL,
+  `update_by` varchar(64) DEFAULT NULL,
+  `update_date` datetime DEFAULT NULL,
+  `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 0正常 1删除',
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_sys_user_id` (`user_id`),
+  INDEX `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库专家审核人员表';

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

@@ -4,13 +4,21 @@
 <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 type="text/javascript">
         var validateForm;
 
-        function doSubmit(i) {
+        function doSubmit() {
             if (validateForm.form()) {
+                // 根据分值模式清理无效字段
+                var pointMode = $("input[name='pointMode']:checked").val();
+                if (pointMode == "1") {
+                    // 固定分值:清空区间字段
+                    $("input[name='pointMin']").val("");
+                    $("input[name='pointMax']").val("");
+                } else if (pointMode == "2") {
+                    // 区间分值:清空固定值
+                    $("input[name='pointValue']").val("");
+                }
                 $("#inputForm").submit();
                 return true;
             }
@@ -18,10 +26,7 @@
         }
 
         $(document).ready(function() {
-            layui.use(['form', 'layer'], function () {
-                var form = layui.form;
-                form.render();
-            });
+            // 初始化表单校验
             validateForm = $("#inputForm").validate({
                 submitHandler: function(form) {
                     loading('正在提交,请稍等...');
@@ -37,7 +42,114 @@
                     }
                 }
             });
+
+            // 初始化字段显隐
+            initFieldVisibility();
+            
+            // 初始化Layui表单并绑定分值模式切换事件
+            layui.use(['form'], function () {
+                var form = layui.form;
+                
+                // 绑定分值模式切换事件
+                form.on('radio(pointModeFilter)', function(data) {
+                    toggleFieldsByMode(data.value);
+                });
+            });
+            
+            // 启动分类变化监听
+            watchCategoryChange();
+            
+            // 初始化规则类型选项(根据当前分类)
+            setTimeout(function() {
+                var categoryId = $("#categoryId").val();
+                var categoryName = $("#categoryName").val();
+                updateRuleTypeOptions(categoryId, categoryName);
+            }, 100);
         });
+
+        // 初始化字段显隐(编辑回显时)
+        function initFieldVisibility() {
+            var pointMode = "${workKnowledgeBasePointRule.pointMode}" || "1";
+            toggleFieldsByMode(pointMode);
+            // 设置默认选中
+            $("input[name='pointMode'][value='" + pointMode + "']").prop("checked", true);
+        }
+
+        // 根据分值模式切换字段显隐
+        function toggleFieldsByMode(mode) {
+            if (mode == "1") {
+                // 固定分值模式
+                $("#pointValueRow").show();
+                $("#pointMinRow").hide();
+                $("#pointMaxRow").hide();
+            } else if (mode == "2") {
+                // 区间分值模式
+                $("#pointValueRow").hide();
+                $("#pointMinRow").show();
+                $("#pointMaxRow").show();
+            }
+        }
+        
+        // 根据归属分类切换规则类型选项
+        function updateRuleTypeOptions(categoryId, categoryName) {
+            console.log("更新规则类型 - categoryId:", categoryId, "categoryName:", categoryName);
+            
+            // 判断是否是基础积分(支持ID或名称匹配)
+            var isBasicPoints = (categoryName && categoryName.indexOf("基础积分") !== -1);
+            console.log("是否为基础积分:", isBasicPoints);
+            
+            if (isBasicPoints) {
+                console.log("切换到阅读/点赞模式");
+                // 基础积分:显示阅读/点赞
+                $("#ruleTypeOption1").html('<input type="radio" name="ruleType" value="10" lay-filter="ruleTypeFilter" style="margin-right:5px;"/> 阅读');
+                $("#ruleTypeOption2").html('<input type="radio" name="ruleType" value="20" lay-filter="ruleTypeFilter" style="margin-right:5px;"/> 点赞');
+                
+                // 如果当前选中的是创建/审核积分,重置为阅读
+                var currentRuleType = $("input[name='ruleType']:checked").val();
+                console.log("当前选中的ruleType:", currentRuleType);
+                if (currentRuleType != "10" && currentRuleType != "20") {
+                    $("input[name='ruleType'][value='10']").prop("checked", true);
+                    console.log("重置为阅读");
+                }
+            } else {
+                console.log("切换到创建积分/审核积分模式");
+                // 其他分类:显示创建积分/审核积分
+                $("#ruleTypeOption1").html('<input type="radio" name="ruleType" value="1" lay-filter="ruleTypeFilter" style="margin-right:5px;"/> 创建积分');
+                $("#ruleTypeOption2").html('<input type="radio" name="ruleType" value="2" lay-filter="ruleTypeFilter" style="margin-right:5px;"/> 审核积分');
+                
+                // 如果当前选中的是阅读/点赞,重置为创建积分
+                var currentRuleType = $("input[name='ruleType']:checked").val();
+                console.log("当前选中的ruleType:", currentRuleType);
+                if (currentRuleType != "1" && currentRuleType != "2") {
+                    $("input[name='ruleType'][value='1']").prop("checked", true);
+                    console.log("重置为创建积分");
+                }
+            }
+            
+            // 重新渲染Layui表单
+            layui.use(['form'], function () {
+                var form = layui.form;
+                form.render('radio');
+                console.log("已重新渲染Layui表单");
+            });
+        }
+        
+        // 监听treeselect的变化
+        function watchCategoryChange() {
+            var lastCategoryId = $("#categoryId").val();
+            var lastCategoryName = $("#categoryName").val();
+            
+            setInterval(function() {
+                var currentCategoryId = $("#categoryId").val();
+                var currentCategoryName = $("#categoryName").val();
+                
+                if (currentCategoryId != lastCategoryId || currentCategoryName != lastCategoryName) {
+                    lastCategoryId = currentCategoryId;
+                    lastCategoryName = currentCategoryName;
+                    updateRuleTypeOptions(currentCategoryId, currentCategoryName);
+                }
+            }, 500); // 每500ms检查一次
+        }
     </script>
 </head>
 <body>
@@ -45,36 +157,91 @@
     <div class="container">
         <form:form id="inputForm" modelAttribute="workKnowledgeBasePointRule" action="${ctx}/workKnowledgeBase/pointRule/save" method="post" class="form-horizontal layui-form">
             <form:hidden path="id"/>
+            <sys:message content="${message}"/>
 
             <div class="form-group layui-row first">
                 <div class="form-group-label"><h2>积分规则信息</h2></div>
 
-                <div class="layui-item layui-col-sm8 lw6">
+                <!-- 第一行:归属分类 + 规则名称 -->
+                <div class="layui-item layui-col-sm6 lw7">
+                    <label class="layui-form-label"><span class="require-item">*</span>归属分类:</label>
+                    <div class="layui-input-block with-icon">
+                        <sys:treeselect id="category" name="categoryId" value="${workKnowledgeBasePointRule.categoryId}" labelName="categoryName"
+                                        labelValue="${workKnowledgeBasePointRule.categoryName}"
+                                        cssStyle="background-color:#fff"
+                                        title="选择分类" url="/workKnowledgeBase/pointCategory/treeData" cssClass="form-control required layui-input"
+                                        allowClear="true" notAllowSelectParent="false"/>
+                    </div>
+                </div>
+                
+                <div class="layui-item layui-col-sm6 lw7">
                     <label class="layui-form-label"><span class="require-item">*</span>规则名称:</label>
                     <div class="layui-input-block">
                         <form:input path="pointName" placeholder="请输入规则名称" htmlEscape="false" maxlength="100" class="form-control layui-input required"/>
                     </div>
                 </div>
 
-                <div class="layui-item layui-col-sm8 lw6">
-                    <label class="layui-form-label"><span class="require-item">*</span>积分值:</label>
-                    <div class="layui-input-block">
-                        <form:input path="pointValue" placeholder="请输入非负整数" htmlEscape="false" maxlength="10" class="form-control layui-input required digits"/>
+                <!-- 第二行:分值模式 + 状态 -->
+                <div class="layui-item layui-col-sm6 lw7">
+                    <label class="layui-form-label"><span class="require-item">*</span>分值模式:</label>
+                    <div class="layui-input-block" style="display:flex; align-items:center; height:38px;">
+                        <label style="display:inline-flex; align-items:center; margin-right:30px; cursor:pointer;">
+                            <input type="radio" name="pointMode" value="1" lay-filter="pointModeFilter" style="margin-right:5px;"/> 固定分值
+                        </label>
+                        <label style="display:inline-flex; align-items:center; cursor:pointer;">
+                            <input type="radio" name="pointMode" value="2" lay-filter="pointModeFilter" style="margin-right:5px;"/> 区间分值
+                        </label>
                     </div>
                 </div>
-
-                <div class="layui-item layui-col-sm8 lw6">
+                
+                <div class="layui-item layui-col-sm6 lw7">
                     <label class="layui-form-label">状态:</label>
                     <div class="layui-input-block" style="display:flex; align-items:center; height:38px;">
                         <label style="display:inline-flex; align-items:center; margin-right:30px; cursor:pointer;">
-                            <input type="radio" name="status" value="1" style="margin-right:5px;" ${workKnowledgeBasePointRule.status == 1 || workKnowledgeBasePointRule.status == null ? 'checked' : ''}/> 启用
+                            <input type="radio" name="status" value="1" style="margin-right:5px;" ${workKnowledgeBasePointRule.status == "1" || workKnowledgeBasePointRule.status == null ? 'checked' : ''}/> 启用
                         </label>
                         <label style="display:inline-flex; align-items:center; cursor:pointer;">
-                            <input type="radio" name="status" value="0" style="margin-right:5px;" ${workKnowledgeBasePointRule.status == 0 ? 'checked' : ''}/> 禁用
+                            <input type="radio" name="status" value="0" style="margin-right:5px;" ${workKnowledgeBasePointRule.status == "0" ? 'checked' : ''}/> 禁用
                         </label>
                     </div>
                 </div>
 
+                <!-- 第三行:规则类型(新增) -->
+                <div class="layui-item layui-col-sm6 lw7">
+                    <label class="layui-form-label"><span class="require-item">*</span>规则类型:</label>
+                    <div class="layui-input-block" style="display:flex; align-items:center; height:38px;" id="ruleTypeContainer">
+                        <!-- 默认显示创建积分/审核积分 -->
+                        <label style="display:inline-flex; align-items:center; margin-right:30px; cursor:pointer;" id="ruleTypeOption1">
+                            <input type="radio" name="ruleType" value="1" lay-filter="ruleTypeFilter" style="margin-right:5px;" ${workKnowledgeBasePointRule.ruleType == "1" || workKnowledgeBasePointRule.ruleType == null ? 'checked' : ''}/> 创建积分
+                        </label>
+                        <label style="display:inline-flex; align-items:center; cursor:pointer;" id="ruleTypeOption2">
+                            <input type="radio" name="ruleType" value="2" lay-filter="ruleTypeFilter" style="margin-right:5px;" ${workKnowledgeBasePointRule.ruleType == "2" ? 'checked' : ''}/> 审核积分
+                        </label>
+                    </div>
+                </div>
+
+                <!-- 第四行:积分值(固定分值模式) -->
+                <div class="layui-item layui-col-sm6 lw7" id="pointValueRow">
+                    <label class="layui-form-label"><span class="require-item">*</span>积分值:</label>
+                    <div class="layui-input-block">
+                        <form:input path="pointValue" placeholder="请输入正整数" htmlEscape="false" maxlength="10" class="form-control layui-input digits"/>
+                    </div>
+                </div>
+
+                <!-- 第五行:积分下限 + 积分上限(区间分值模式) -->
+                <div class="layui-item layui-col-sm6 lw7" id="pointMinRow" style="display:none;">
+                    <label class="layui-form-label"><span class="require-item">*</span>积分下限:</label>
+                    <div class="layui-input-block">
+                        <form:input path="pointMin" placeholder="请输入正整数" htmlEscape="false" maxlength="10" class="form-control layui-input digits"/>
+                    </div>
+                </div>
+                
+                <div class="layui-item layui-col-sm6 lw7" id="pointMaxRow" style="display:none;">
+                    <label class="layui-form-label"><span class="require-item">*</span>积分上限:</label>
+                    <div class="layui-input-block">
+                        <form:input path="pointMax" placeholder="请输入正整数" htmlEscape="false" maxlength="10" class="form-control layui-input digits"/>
+                    </div>
+                </div>
 
             </div>
 
@@ -82,5 +249,8 @@
         </form:form>
     </div>
 </div>
+<!-- layui.all.js 必须放在 body 最底部 -->
+<script src="${ctxStatic}/layer-v2.3/layui/layui.all.js" charset="utf-8"></script>
+
 </body>
 </html>

+ 58 - 10
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBasePointRuleList.jsp

@@ -115,6 +115,8 @@
 				<form:form id="searchForm" modelAttribute="workKnowledgeBasePointRule" action="${ctx}/workKnowledgeBase/pointRule/list" method="post" class="form-inline">
 					<input id="pageNo" name="pageNo" type="hidden" value="${page.pageNo}"/>
 					<input id="pageSize" name="pageSize" type="hidden" value="${page.pageSize}"/>
+					<!-- 隐藏域:分类ID(左侧树点击时赋值) -->
+					<input type="hidden" name="categoryId" value="${workKnowledgeBasePointRule.categoryId}"/>
 					<table:sortColumn id="orderBy" name="orderBy" value="${page.orderBy}" callback="sortOrRefresh();"/>
 					<div class="commonQuery">
 						<div class="layui-item query athird">
@@ -155,7 +157,7 @@
 				<div class="nav-btns">
 					<div class="layui-btn-group">
 						<shiro:hasPermission name="workKnowledgeBase:pointRule:add">
-							<table:addRow label="新增积分规则" url="${ctx}/workKnowledgeBase/pointRule/form" title="新增积分规则" height="400px" width="800px"></table:addRow>
+							<button class="layui-btn layui-btn-sm" onclick="openAddRuleDialog()">新增积分规则</button>
 						</shiro:hasPermission>
 						<button class="layui-btn layui-btn-sm" data-toggle="tooltip" data-placement="left" onclick="sortOrRefresh()" title="刷新"> 刷新</button>
 					</div>
@@ -172,6 +174,44 @@
 </div>
 <script src="${ctxStatic}/layer-v2.3/layui/layui.all.js" charset="utf-8"></script>
 <script>
+    // 打开新增规则弹窗
+    function openAddRuleDialog() {
+        // 从 URL 参数中获取当前选中的分类ID
+        var urlParams = new URLSearchParams(window.location.search);
+        var categoryId = urlParams.get('categoryId');
+        
+        var url = '${ctx}/workKnowledgeBase/pointRule/form';
+        if (categoryId) {
+            url += '?categoryId=' + categoryId;
+        }
+        
+        top.layer.open({
+            type: 2,
+            area: ['1000px', '600px'],
+            title: '新增积分规则',
+            maxmin: true,
+            content: url,
+            skin: 'three-btns',
+            btn: ['提交', '关闭'],
+            btn1: function(index, layero){
+                var body = top.layer.getChildFrame('body', index);
+                var iframeWin = layero.find('iframe')[0];
+                var inputForm = body.find('#inputForm');
+                var top_iframe = top.getActiveTab().attr("name");
+                inputForm.attr("target", top_iframe);
+                if(iframeWin.contentWindow.doSubmit()){
+                    setTimeout(function(){
+                        top.layer.close(index);
+                        // 刷新列表
+                        window.location.reload();
+                    }, 100);
+                }
+            },
+            btn2: function (index) {
+            }
+        });
+    }
+
     layui.use('table', function () {
         layui.table.render({
             limit: ${ page.pageSize }
@@ -180,13 +220,17 @@
             , page: false
             , cols: [[
                 {field: 'index', align: 'center', width: 40, title: '序号'}
-                , {field: 'pointName', align: 'center', title: '规则名称', minWidth: 200}
-                , {field: 'pointValue', align: 'center', title: '积分值', width: 120, templet: function (d) {
+                , {field: 'categoryName', align: 'center', title: '归属分类', minWidth: 150}
+                , {field: 'pointName', align: 'center', title: '规则名称', minWidth: 180}
+                , {field: 'pointDisplay', align: 'center', title: '积分展示', width: 140, templet: function (d) {
+                    if (d.pointMode == "2") {
+                        return '<span style="color:#1aa094;font-weight:bold;">' + (d.pointMin || 0) + '~' + (d.pointMax || 0) + ' 分</span>';
+                    }
                     return '<span style="color:#1aa094;font-weight:bold;">' + (d.pointValue || 0) + ' 分</span>';
                 }}
                 , {field: 'status', align: 'center', title: '状态', width: 100, templet: function (d) {
-                    var st = parseInt(d.status);
-                    if (st === 1) return '<span class="layui-badge layui-bg-green">启用</span>';
+                    var st = d.status;
+                    if (st == "1") return '<span class="layui-badge layui-bg-green">启用</span>';
                     return '<span class="layui-badge layui-bg-gray">禁用</span>';
                 }}
                 , {field: 'createName', align: 'center', title: '创建人', width: 120}
@@ -194,12 +238,12 @@
                 , {align: 'center', title: "操作", width: 240, templet: function (d) {
                     var xml = "<div class=\"layui-btn-group\">";
                     <shiro:hasPermission name="workKnowledgeBase:pointRule:edit">
-                    xml += "<a href=\"javascript:void(0)\" onclick=\"openDialog('修改积分规则', '${ctx}/workKnowledgeBase/pointRule/form?id=" + d.id + "','800px', '400px')\" class=\"layui-btn layui-btn-xs  layui-bg-green\"> 修改</a>";
-                    var st = parseInt(d.status);
-                    if (st === 1) {
-                        xml += "<a href=\"javascript:void(0)\" onclick=\"toggleStatus('禁用规则', '" + d.id + "', 0)\" class=\"layui-btn layui-btn-xs  layui-bg-orange\"> 禁用</a>";
+                    xml += "<a href=\"javascript:void(0)\" onclick=\"openDialog('修改积分规则', '${ctx}/workKnowledgeBase/pointRule/form?id=" + d.id + "','800px', '450px')\" class=\"layui-btn layui-btn-xs  layui-bg-green\"> 修改</a>";
+                    var st = d.status;
+                    if (st == "1") {
+                        xml += "<a href=\"javascript:void(0)\" onclick=\"toggleStatus('禁用规则', '" + d.id + "', '0')\" class=\"layui-btn layui-btn-xs  layui-bg-orange\"> 禁用</a>";
                     } else {
-                        xml += "<a href=\"javascript:void(0)\" onclick=\"toggleStatus('启用规则', '" + d.id + "', 1)\" class=\"layui-btn layui-btn-xs  layui-bg-cyan\"> 启用</a>";
+                        xml += "<a href=\"javascript:void(0)\" onclick=\"toggleStatus('启用规则', '" + d.id + "', '1')\" class=\"layui-btn layui-btn-xs  layui-bg-cyan\"> 启用</a>";
                     }
                     </shiro:hasPermission>
                     <shiro:hasPermission name="workKnowledgeBase:pointRule:del">
@@ -216,8 +260,12 @@
                 {
                     "index":"${index.index + 1 + (page.pageNo - 1) * page.pageSize}"
                     ,"id":"${workKnowledgeBasePointRule.id}"
+                    ,"categoryName":"<c:out value='${workKnowledgeBasePointRule.categoryName}'/>"
                     ,"pointName":"<c:out value='${workKnowledgeBasePointRule.pointName}'/>"
+                    ,"pointMode":"${workKnowledgeBasePointRule.pointMode}"
                     ,"pointValue":"${workKnowledgeBasePointRule.pointValue}"
+                    ,"pointMin":"${workKnowledgeBasePointRule.pointMin}"
+                    ,"pointMax":"${workKnowledgeBasePointRule.pointMax}"
                     ,"status":"${workKnowledgeBasePointRule.status}"
                     ,"createName":"<c:out value='${workKnowledgeBasePointRule.createBy.name}'/>"
                     ,"updateDate":"<fmt:formatDate value='${workKnowledgeBasePointRule.updateDate}' pattern='yyyy-MM-dd HH:mm:ss'/>"

+ 18 - 10
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareList.jsp

@@ -503,46 +503,51 @@
                     var xml = "<span onclick=\"openDetailDialog('" + d.id + "')\" class=\"status-label status-label-" + st.label + "\" >" + st.status + "</span>";
                     return xml;
                 }},
-            {align: 'center', title: '操作', width: 260, fixed: 'right', templet: function(d) {
+                {align: 'center', title: '操作', width: 260, fixed: 'right', templet: function(d) {
                 var xml = '<div class="layui-btn-group">';
                 var status = parseInt(d.auditStatus);
                 var isCreator = (d.submitAuditUserId) === d.userid;
-
+                            
+                // 判断是否为需要专家审核的特殊分类
+                var needsExpertAudit = (d.treeName === '技术总结' || d.treeName === '培训心得');
+                // 判断当前用户是否有审核权限(管理员、专家或普通分类)
+                var canAudit = !isCreator && !d.currentUserAudited && (d.isAdmin || d.isExpert || !needsExpertAudit);
+            
                 // 草稿0:可修改、删除
                 if (status === 0) {
                     xml += '<a href="javascript:void(0)" onclick="openDialog(\'编辑文件\',\'${ctx}/workKnowledgeBase/share/form?id=' + d.id + '&treeNodeId=${treeNodeId}&rootId=${rootId}\',\'80%\',\'80%\')" class="layui-btn layui-btn-xs layui-bg-green"> 修改</a>';
                     xml += '<a href="javascript:void(0)" onclick="deleteShare(\'' + d.id + '\')" class="layui-btn layui-btn-xs layui-bg-red"> 删除</a>';
                 }
-
-                // 未审核 1/审核中2:撤回(提交审核人/管理员)、查看审核(非提交审核人)
+            
+                // 未审核 1/审核中2:撤回(提交审核人/管理员)、查看/审核(非提交审核人且有权限
                 if (status === 1 || status === 2) {
                     // 撤回:提交审核人/管理员
                     if (isCreator || d.isAdmin) {
                         xml += '<a href="javascript:void(0)" onclick="withdrawAudit(\'' + d.id + '\')" class="layui-btn layui-btn-xs layui-bg-orange"> 撤回</a>';
                     }
-                    // 查看/审核:非提交审核人且当前登录人未审核过时才展示审核按钮
-                    if (!isCreator && !d.currentUserAudited) {
+                    // 审核:非提交审核人且当前登录人未审核过且有权限
+                    if (canAudit) {
                         xml += '<a href="javascript:void(0)" onclick="openAuditDetailDialog(\'' + d.id + '\')" class="layui-btn layui-btn-xs layui-bg-blue"> 审核</a>';
                     }
                 }
-
+            
                 // 审核未通过4:可修改、删除
                 if (status === 4) {
                     xml += '<a href="javascript:void(0)" onclick="openDialog(\'编辑文件\',\'${ctx}/workKnowledgeBase/share/form?id=' + d.id + '&treeNodeId=${treeNodeId}&rootId=${rootId}\',\'80%\',\'80%\')" class="layui-btn layui-btn-xs layui-bg-green"> 修改</a>';
                     xml += '<a href="javascript:void(0)" onclick="deleteShare(\'' + d.id + '\')" class="layui-btn layui-btn-xs layui-bg-red"> 删除</a>';
                 }
-
+            
                 // 审核通过5:普通用户仅查看,管理员可修改删除
                 if (status === 5 && d.isAdmin) {
                     xml += '<a href="javascript:void(0)" onclick="openDialog(\'编辑文件\',\'${ctx}/workKnowledgeBase/share/form?id=' + d.id + '&treeNodeId=${treeNodeId}&rootId=${rootId}\',\'80%\',\'80%\')" class="layui-btn layui-btn-xs layui-bg-green"> 修改</a>';
                     xml += '<a href="javascript:void(0)" onclick="deleteShare(\'' + d.id + '\')" class="layui-btn layui-btn-xs layui-bg-red"> 删除</a>';
                 }
-
+            
                 // 审核记录按钮(审核中和审核通过/未通过时展示)
                 if (status === 1 || status === 2 || status === 4 || status === 5) {
                     xml += '<a href="javascript:void(0)" onclick="viewAuditRecords(\'' + d.id + '\')" class="layui-btn layui-btn-xs"> 记录</a>';
                 }
-
+            
                 xml += '</div>';
                 return xml;
             }}
@@ -573,9 +578,12 @@
                     ,"userid": "${fns:getUser().id}"
                     ,"createbyid": "${row.createById}"
                     ,"isAdmin": ${row.isAdmin == 'true' ? 'true' : 'false'}
+                    ,"isExpert": ${row.isExpert == true ? 'true' : 'false'}
                     ,"submitAuditUserId": "${row.submitAuditUserId}"
                     ,"contentAttribute": "${row.contentAttribute}"
                     ,"auditStatus": ${row.auditStatus != null ? row.auditStatus : 0}
+                    ,"treeNodeId": "${row.treeNodeId}"
+                    ,"treeName": "<c:out value='${row.treeName}'/>"
                     <c:forEach items="${dynamicFields}" var="field">
                     ,"dynamic_${field.fieldKey}": "<c:out value='${row["dynamic_".concat(field.fieldKey)]}'/>"
 

+ 244 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeExpertForm.jsp

@@ -0,0 +1,244 @@
+<%@ 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">
+        var validateForm;
+
+        /**
+         * 弹窗提交函数
+         */
+        function doSubmit() {
+            if (validateForm.form()) {
+                $("#inputForm").submit();
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * 选择系统用户回调函数
+         */
+        function selectUserInfo(data) {
+            if (data && data.userId) {
+                // 填充隐藏的用户ID
+                $("#userId").val(data.userId);
+                // 填充专家姓名(从sys_user.name获取)
+                $("#expertName").val(data.name).attr("readonly", "readonly");
+                // 填充联系电话(从sys_user.mobile获取)
+                $("#phone").val(data.mobile || '').attr("readonly", "readonly");
+                // 显示提示信息
+                top.layer.msg("已选择用户:" + data.name, {icon: 1, time: 1500});
+            }
+        }
+
+        /**
+         * 打开用户选择弹窗
+         */
+        function openUserSelect() {
+            top.layer.open({
+                type: 2,
+                area: ['90%', '90%'],
+                title: "选择系统用户",
+                skin: "two-btns",
+                maxmin: true,
+                content: encodeURI("${ctx}/sys/office/gridSelectUser?url=${ctx}/sys/office/gridSelectUser&isAll=true&type=null&isSingle=true"),
+                btn: ['确定', '关闭'],
+                yes: function(index, layero){
+                    var iframeWin = layero.find('iframe')[0].contentWindow;
+                    var item = iframeWin.getSelectedItem();
+                    if(item == "-1"){
+                        return;
+                    }
+                    selectUserInfo(item);
+                    top.layer.close(index);
+                },
+                cancel: function(index){
+                }
+            });
+        }
+
+        /**
+         * 清除用户选择
+         */
+        function clearUserSelect() {
+            $("#userId").val("");
+            $("#expertName").val("").removeAttr("readonly");
+            $("#phone").val("").removeAttr("readonly");
+        }
+
+        $(document).ready(function() {
+            // DOM加载完成后先初始化表单校验
+            validateForm = $("#inputForm").validate({
+                rules: {
+                    userId: {
+                        required: true
+                    },
+                    expertName: {
+                        required: true,
+                        maxlength: 50
+                    },
+                    phone: {
+                        maxlength: 20
+                    },
+                    sort: {
+                        digits: true
+                    }
+                },
+                messages: {
+                    userId: {
+                        required: "请选择绑定的系统用户"
+                    },
+                    expertName: {
+                        required: "请输入专家姓名",
+                        maxlength: "专家姓名不能超过50个字符"
+                    },
+                    phone: {
+                        maxlength: "联系电话不能超过20个字符"
+                    },
+                    sort: {
+                        digits: "排序号必须为数字"
+                    }
+                },
+                submitHandler: function(form) {
+                    loading('正在提交,请稍等...');
+                    form.submit();
+                },
+                errorContainer: "#messageBox",
+                errorPlacement: function(error, element) {
+                    $("#messageBox").text("输入有误,请先更正。");
+                    if (element.is(":checkbox") || element.is(":radio") || element.parent().is(".input-append")) {
+                        error.appendTo(element.parent().parent());
+                    } else {
+                        error.insertAfter(element);
+                    }
+                }
+            });
+
+            // 如果已有userId(编辑场景),禁用用户选择按钮
+            <c:if test="${not empty workKnowledgeExpert.userId}">
+                $("#selectUserBtn").attr("disabled", "disabled").addClass("disabled");
+                top.layer.msg("编辑模式下不可修改绑定用户", {icon: 0, time: 2000});
+            </c:if>
+        });
+    </script>
+</head>
+<body>
+<div class="single-form">
+    <div class="container">
+        <form:form id="inputForm" modelAttribute="workKnowledgeExpert" action="${ctx}/workKnowledgeBase/expert/save" method="post" class="form-horizontal layui-form">
+            <form:hidden path="id"/>
+            <!-- 隐藏的系统用户ID字段 -->
+            <input type="hidden" id="userId" name="userId" value="${workKnowledgeExpert.userId}"/>
+            
+            <sys:message content="${message}"/>
+
+            <div class="form-group layui-row first">
+                <div class="form-group-label"><h2>专家基本信息</h2></div>
+
+                <!-- 第一行:选择系统用户 + 专家姓名 -->
+                <div class="layui-item layui-col-sm6 lw7">
+                    <label class="layui-form-label"><span class="require-item">*</span>绑定系统用户:</label>
+                    <div class="layui-input-block with-icon">
+                        <div class="input-group">
+                            <input type="text" id="userNameDisplay" 
+                                   value="<c:if test='${not empty workKnowledgeExpert.userId}'>已绑定用户(不可修改)</c:if>"
+                                   readonly="readonly" 
+                                   class="form-control layui-input"
+                                   placeholder="请点击右侧按钮选择系统用户"/>
+                            <span class="input-group-btn">
+                                <button type="button" id="selectUserBtn" class="btn btn-primary" onclick="openUserSelect()" 
+                                        <c:if test='${not empty workKnowledgeExpert.userId}'>disabled="disabled"</c:if>>
+                                    <i class="fa fa-user"></i> 选择用户
+                                </button>
+                            </span>
+                            <c:if test="${empty workKnowledgeExpert.userId}">
+                                <span class="input-group-btn">
+                                    <button type="button" class="btn btn-default" onclick="clearUserSelect()">
+                                        <i class="fa fa-times"></i> 清除
+                                    </button>
+                                </span>
+                            </c:if>
+                        </div>
+                        <p class="help-block text-muted">选择一个系统用户进行绑定,同一个用户只能绑定一位专家</p>
+                    </div>
+                </div>
+
+                <div class="layui-item layui-col-sm6 lw7">
+                    <label class="layui-form-label"><span class="require-item">*</span>专家姓名:</label>
+                    <div class="layui-input-block">
+                        <input type="text" id="expertName" name="expertName" 
+                               value="${workKnowledgeExpert.expertName}" 
+                               placeholder="选择用户后自动填充" 
+                               readonly="readonly"
+                               class="form-control layui-input required"/>
+                    </div>
+                </div>
+
+                <!-- 第二行:联系电话 + 专业领域 -->
+                <div class="layui-item layui-col-sm6 lw7">
+                    <label class="layui-form-label">联系电话:</label>
+                    <div class="layui-input-block">
+                        <input type="text" id="phone" name="phone" 
+                               value="${workKnowledgeExpert.phone}" 
+                               placeholder="选择用户后自动填充" 
+                               readonly="readonly"
+                               class="form-control layui-input"/>
+                    </div>
+                </div>
+
+                <div class="layui-item layui-col-sm6 lw7">
+                    <label class="layui-form-label">专业领域:</label>
+                    <div class="layui-input-block">
+                        <form:input path="professionalField" placeholder="请输入专业领域,如:技术总结、培训心得等" 
+                                    htmlEscape="false" maxlength="255" 
+                                    class="form-control layui-input"/>
+                    </div>
+                </div>
+
+                <!-- 第三行:专家简介(全宽) -->
+                <div class="layui-item layui-col-sm12">
+                    <label class="layui-form-label">专家简介:</label>
+                    <div class="layui-input-block">
+                        <form:textarea path="introduction" placeholder="请输入专家简介、擅长审核的文档类型等" 
+                                       htmlEscape="false" rows="4" 
+                                       class="form-control layui-textarea"/>
+                    </div>
+                </div>
+
+                <!-- 第四行:排序号 + 状态 -->
+                <div class="layui-item layui-col-sm6 lw7">
+                    <label class="layui-form-label">排序号:</label>
+                    <div class="layui-input-block">
+                        <form:input path="sort" placeholder="数值越小越靠前" 
+                                    htmlEscape="false" maxlength="10" 
+                                    class="form-control layui-input digits"/>
+                    </div>
+                </div>
+
+                <div class="layui-item layui-col-sm6 lw7">
+                    <label class="layui-form-label">状态:</label>
+                    <div class="layui-input-block" style="display:flex; align-items:center; height:38px;">
+                        <label style="display:inline-flex; align-items:center; margin-right:30px; cursor:pointer;">
+                            <input type="radio" name="status" value="1" style="margin-right:5px;" 
+                                   ${workKnowledgeExpert.status == "1" || workKnowledgeExpert.status == null ? 'checked' : ''}/> 启用
+                        </label>
+                        <label style="display:inline-flex; align-items:center; cursor:pointer;">
+                            <input type="radio" name="status" value="0" style="margin-right:5px;" 
+                                   ${workKnowledgeExpert.status == "0" ? 'checked' : ''}/> 禁用
+                        </label>
+                    </div>
+                </div>
+
+            </div>
+
+            <div class="form-group layui-row page-end"></div>
+        </form:form>
+    </div>
+</div>
+<!-- layui.all.js 必须放在 body 最底部 -->
+<script src="${ctxStatic}/layer-v2.3/layui/layui.all.js" charset="utf-8"></script>
+</body>
+</html>

+ 274 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeExpertList.jsp

@@ -0,0 +1,274 @@
+<%@ 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">
+		$(document).ready(function() {
+			// 搜索框展开/收起
+			$('#moresee').click(function(){
+				if($('#moresees').is(':visible')) {
+					$('#moresees').slideUp(0, resizeListWindow1);
+					$('#moresee i').removeClass("glyphicon glyphicon-menu-up").addClass("glyphicon glyphicon-menu-down");
+				} else {
+					$('#moresees').slideDown(0, resizeListWindow1);
+					$('#moresee i').removeClass("glyphicon glyphicon-menu-down").addClass("glyphicon glyphicon-menu-up");
+				}
+			});
+		});
+
+		/** 查询 */
+		function search() {
+			$('#searchForm').submit();
+			return false;
+		}
+
+		/** 重置查询 */
+		function resetSearch() {
+			$('#searchForm input[type=text]').val('');
+			$('#searchForm select').val('');
+			$('#searchForm').submit();
+		}
+
+		/** 打开新增/编辑弹窗 */
+		function openDialog(title, url, width, height) {
+			if (navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i)) {
+				width = 'auto'; height = 'auto';
+			}
+			top.layer.open({
+				type: 2,
+				area: [width, height],
+				title: title,
+				maxmin: true,
+				content: url,
+				skin: 'three-btns',
+				btn: ['提交', '关闭'],
+				btn1: function(index, layero) {
+					var iframeWin = layero.find('iframe')[0];
+					if (iframeWin.contentWindow.doSubmit()) {
+						setTimeout(function() {
+							top.layer.close(index);
+							search(); // 刷新列表
+						}, 300);
+					}
+				},
+				btn2: function(index) { top.layer.close(index); }
+			});
+		}
+
+		/** 删除专家 */
+		function deleteExpert(id) {
+			layer.open({
+				title: '删除确认',
+				content: '确认要删除该专家记录吗?',
+				skin: 'two-btns',
+				btn: ['确定', '取消'],
+				btn1: function(index) {
+					$.ajax({
+						type: 'POST',
+						url: '${ctx}/workKnowledgeBase/expert/delete?id=' + id,
+						success: function(data) {
+							if (data.success) {
+								layer.msg('删除成功', {icon: 1});
+								layer.close(index);
+								search();
+							} else {
+								layer.msg(data.msg || '删除失败', {icon: 0});
+							}
+						}
+					});
+				},
+				btn2: function(index) { layer.close(index); }
+			});
+		}
+
+		/** 切换专家状态 */
+		function toggleStatus(id, currentStatus) {
+			var newStatus = currentStatus == '1' ? '0' : '1';
+			var actionText = newStatus == '1' ? '启用' : '禁用';
+			layer.open({
+				title: actionText + '确认',
+				content: '确认要' + actionText + '该专家吗?',
+				skin: 'two-btns',
+				btn: ['确定', '取消'],
+				btn1: function(index) {
+					$.ajax({
+						type: 'POST',
+						url: '${ctx}/workKnowledgeBase/expert/toggleStatus',
+						data: {id: id, status: newStatus},
+						success: function(data) {
+							if (data.success) {
+								layer.msg(data.msg, {icon: 1});
+								layer.close(index);
+								search();
+							} else {
+								layer.msg(data.msg || '操作失败', {icon: 0});
+							}
+						}
+					});
+				},
+				btn2: function(index) { layer.close(index); }
+			});
+		}
+
+		resizeListWindow1 = function() {
+			var height = $(window).height() - 10;
+			$('body').css('min-height', height + 'px');
+		};
+		$(window).resize(function() { resizeListWindow1(); });
+	</script>
+	<style>
+		body {
+			background-color: transparent;
+			filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#26FFFFFF, endColorstr=#26FFFFFF);
+			background-color: rgba(255,255,255,0);
+		}
+	</style>
+</head>
+<body>
+<div class="wrapper wrapper-content">
+	<sys:message content="${message}"/>
+	<div class="layui-row">
+		<!-- 查询条件区域 -->
+		<div class="full-width fl">
+			<div class="contentShadow layui-row" id="queryDiv">
+				<form:form id="searchForm" modelAttribute="workKnowledgeExpert" action="${ctx}/workKnowledgeBase/expert/list" method="post" class="form-inline">
+					<input id="pageNo" name="pageNo" type="hidden" value="${page.pageNo}"/>
+					<input id="pageSize" name="pageSize" type="hidden" value="${page.pageSize}"/>
+					<div class="commonQuery lw7">
+						<div class="layui-item query athird">
+							<label class="layui-form-label">专家姓名:</label>
+							<div class="layui-input-block with-icon">
+								<input type="text" name="expertName" value="${workKnowledgeExpert.expertName}"
+								       placeholder="专家姓名模糊查询" class="form-control layui-input"/>
+							</div>
+						</div>
+						<div class="layui-item query athird">
+							<label class="layui-form-label">专业领域:</label>
+							<div class="layui-input-block with-icon">
+								<input type="text" name="professionalField" value="${workKnowledgeExpert.professionalField}"
+								       placeholder="专业领域模糊查询" class="form-control layui-input"/>
+							</div>
+						</div>
+						<div class="layui-item query athird">
+							<label class="layui-form-label">状态:</label>
+							<div class="layui-input-block with-icon">
+								<select name="status" class="form-control simple-select">
+									<option value="">全部</option>
+									<option value="1" ${workKnowledgeExpert.status == '1' ? 'selected' : ''}>启用</option>
+									<option value="0" ${workKnowledgeExpert.status == '0' ? 'selected' : ''}>禁用</option>
+								</select>
+							</div>
+						</div>
+						<div class="layui-item athird">
+							<div class="input-group">
+								<a href="#" id="moresee"><i class="glyphicon glyphicon-menu-down"></i></a>
+								<div class="layui-btn-group search-spacing">
+									<button id="searchQuery" class="layui-btn layui-btn-sm layui-bg-blue" onclick="return search()">查询</button>
+									<button id="searchReset" class="layui-btn layui-btn-sm" onclick="resetSearch()">重置</button>
+								</div>
+							</div>
+						</div>
+						<div style="clear:both;"></div>
+					</div>
+				</form:form>
+			</div>
+		</div>
+
+		<!-- 工具栏 + 表格 -->
+		<div class="full-width fl">
+			<div class="contentShadow layui-form contentDetails">
+				<div class="nav-btns">
+					<div class="layui-btn-group">
+						<shiro:hasPermission name="workKnowledgeBase:expert:add">
+							<button class="layui-btn layui-btn-sm layui-bg-blue"
+							        onclick="openDialog('新增专家','${ctx}/workKnowledgeBase/expert/form','90%','90%')">
+								<i class="glyphicon glyphicon-plus"></i> 新增
+							</button>
+						</shiro:hasPermission>
+						<button class="layui-btn layui-btn-sm" onclick="search()" title="刷新">刷新</button>
+					</div>
+					<div style="clear:both;"></div>
+				</div>
+
+				<!-- 使用Layui table渲染 -->
+				<table class="oa-table layui-table" id="contentTable"></table>
+
+				<!-- 分页代码 -->
+				<table:page page="${page}"></table:page>
+				<div style="clear:both;"></div>
+			</div>
+		</div>
+	</div>
+</div>
+<script src="${ctxStatic}/layer-v2.3/layui/layui.all.js" charset="utf-8"></script>
+<script>
+	layui.use('table', function(){
+		layui.table.render({
+			limit: ${page.pageSize},
+			elem: '#contentTable',
+			page: false,
+			cols: [[
+				{field:'index', align:'center', title: '序号', width: 60, templet: function(d){
+					return d.LAY_TABLE_INDEX + 1 + (${page.pageNo} - 1) * ${page.pageSize};
+				}},
+				{field:'expertName', align: 'center', title: '专家姓名', minWidth: 120},
+				{field:'userName', align: 'center', title: '绑定系统用户', minWidth: 140, templet: function(d) {
+					return d.userName || ('用户ID: ' + d.userId);
+				}},
+				{field:'professionalField', align: 'center', title: '专业领域', minWidth: 160, templet: function(d) {
+					var field = d.professionalField || '';
+					return field.length > 20 ? field.substring(0, 20) + '...' : field;
+				}},
+				{field:'phone', align: 'center', title: '联系电话', minWidth: 130},
+				{field:'status', align: 'center', title: '状态', width: 90, templet: function(d) {
+					if (d.status == '1') {
+						return '<span class="status-label status-label-signed">启用</span>';
+					} else {
+						return '<span class="status-label status-label-tempstore">禁用</span>';
+					}
+				}},
+				{field:'sort', align: 'center', title: '排序', width: 80},
+				{align: 'center', title: '操作', width: 240, fixed: 'right', templet: function(d) {
+					var xml = '<div class="layui-btn-group">';
+					<shiro:hasPermission name="workKnowledgeBase:expert:edit">
+					xml += '<a href="javascript:void(0)" onclick="openDialog(\'编辑专家\',\'${ctx}/workKnowledgeBase/expert/form?id=' + d.id + '\',\'90%\',\'90%\')" class="layui-btn layui-btn-xs layui-bg-green"> 修改</a>';
+					if (d.status == '1') {
+						xml += '<a href="javascript:void(0)" onclick="toggleStatus(\'' + d.id + '\',\'' + d.status + '\')" class="layui-btn layui-btn-xs layui-bg-orange"> 禁用</a>';
+					} else {
+						xml += '<a href="javascript:void(0)" onclick="toggleStatus(\'' + d.id + '\',\'' + d.status + '\')" class="layui-btn layui-btn-xs layui-bg-blue"> 启用</a>';
+					}
+					</shiro:hasPermission>
+					<shiro:hasPermission name="workKnowledgeBase:expert:del">
+					xml += '<a href="javascript:void(0)" onclick="deleteExpert(\'' + d.id + '\')" class="layui-btn layui-btn-xs layui-bg-red"> 删除</a>';
+					</shiro:hasPermission>
+					xml += '</div>';
+					return xml;
+				}}
+			]],
+			data: [
+				<c:choose>
+				<c:when test="${not empty page.list}">
+				<c:forEach items="${page.list}" var="row" varStatus="st">
+				<c:if test="${st.index != 0}">,</c:if>
+				{
+					"id": "${row.id}",
+					"userId": "${row.userId}",
+					"userName": "",
+					"expertName": "<c:out value='${row.expertName}'/>",
+					"professionalField": "<c:out value='${row.professionalField}'/>",
+					"phone": "<c:out value='${row.phone}'/>",
+					"status": "${row.status}",
+					"sort": "${row.sort}"
+				}
+				</c:forEach>
+				</c:when>
+				<c:otherwise></c:otherwise>
+				</c:choose>
+			]
+		});
+	});
+</script>
+</body>
+</html>

+ 94 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgePointCategoryForm.jsp

@@ -0,0 +1,94 @@
+<%@ 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">
+        var validateForm;
+
+        function doSubmit() {
+            if (validateForm.form()) {
+                $("#inputForm").submit();
+                return true;
+            }
+            return false;
+        }
+
+        $(document).ready(function() {
+            // 初始化表单校验
+            validateForm = $("#inputForm").validate({
+                submitHandler: function(form) {
+                    loading('正在提交,请稍等...');
+                    form.submit();
+                },
+                errorContainer: "#messageBox",
+                errorPlacement: function(error, element) {
+                    $("#messageBox").text("输入有误,请先更正。");
+                    if (element.is(":checkbox") || element.is(":radio") || element.parent().is(".input-append")) {
+                        error.appendTo(element.parent().parent());
+                    } else {
+                        error.insertAfter(element);
+                    }
+                }
+            });
+        });
+    </script>
+</head>
+<body>
+<div class="single-form">
+    <div class="container">
+        <form:form id="inputForm" modelAttribute="category" action="${ctx}/workKnowledgeBase/pointCategory/save" method="post" class="form-horizontal layui-form">
+            <form:hidden path="id"/>
+            <sys:message content="${message}"/>
+
+            <div class="form-group layui-row first">
+                <div class="form-group-label"><h2>分类信息</h2></div>
+
+                <!-- 父分类 -->
+                <div class="layui-item layui-col-sm8 lw6">
+                    <label class="layui-form-label">父分类:</label>
+                    <div class="layui-input-block">
+                        <input type="text" value="${category.parent.categoryName}" readonly="readonly" class="form-control layui-input"/>
+                        <form:hidden path="parent.id"/>
+                    </div>
+                </div>
+
+                <!-- 分类名称 -->
+                <div class="layui-item layui-col-sm8 lw6">
+                    <label class="layui-form-label"><span class="require-item">*</span>分类名称:</label>
+                    <div class="layui-input-block">
+                        <form:input path="categoryName" placeholder="请输入分类名称" htmlEscape="false" maxlength="64" class="form-control layui-input required"/>
+                    </div>
+                </div>
+
+                <!-- 排序号 -->
+                <div class="layui-item layui-col-sm8 lw6">
+                    <label class="layui-form-label">排序号:</label>
+                    <div class="layui-input-block">
+                        <form:input path="sort" placeholder="请输入排序号" htmlEscape="false" maxlength="10" class="form-control layui-input digits"/>
+                    </div>
+                </div>
+
+                <!-- 状态 -->
+                <div class="layui-item layui-col-sm8 lw6">
+                    <label class="layui-form-label">状态:</label>
+                    <div class="layui-input-block" style="display:flex; align-items:center; height:38px;">
+                        <label style="display:inline-flex; align-items:center; margin-right:30px; cursor:pointer;">
+                            <input type="radio" name="status" value="1" style="margin-right:5px;" ${category.status == "1" || category.status == null ? 'checked' : ''}/> 启用
+                        </label>
+                        <label style="display:inline-flex; align-items:center; cursor:pointer;">
+                            <input type="radio" name="status" value="0" style="margin-right:5px;" ${category.status == "0" ? 'checked' : ''}/> 禁用
+                        </label>
+                    </div>
+                </div>
+
+            </div>
+
+            <div class="form-group layui-row page-end"></div>
+        </form:form>
+    </div>
+</div>
+<script src="${ctxStatic}/layer-v2.3/layui/layui.all.js" charset="utf-8"></script>
+</body>
+</html>

+ 95 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgePointCategoryList.jsp

@@ -0,0 +1,95 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp"%>
+<html>
+<head>
+	<title>知识库-积分规则配置</title>
+	<meta name="decorator" content="default"/>
+	<%@include file="/webpage/include/treeview.jsp" %>
+	<style type="text/css">
+		.ztree {overflow:auto;margin:0;_margin-top:10px;padding:10px 0 0 10px;}
+		.ztree li a.curSelectedNode {
+			background-color: #1aa094;
+			color: #fff;
+			opacity: 0.8;
+		}
+	</style>
+	<script type="text/javascript">
+        function refresh(){//刷新
+            window.location="${ctx}/workKnowledgeBase/pointCategory/";
+        }
+	</script>
+	<style>
+		body{
+			background-color:transparent;
+			filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#26FFFFFF, endColorstr=#26FFFFFF);
+			color:#ffffff;
+			background-color:rgba(255,255,255,0);
+			height:100%;
+		}
+	</style>
+</head>
+<body>
+<div class="wrapper wrapper-content full-width" id="divId">
+	<sys:message content="${message}"/>
+	<div id="content" class="pr full-height full-width">
+
+		<div id="left"  class="contentShadow fl contents">
+			<div class="ztreeContainer">
+				<div id="ztree" class="ztree leftBox-content"></div>
+			</div>
+		</div>
+
+
+		<div id="right"  class="fl contents">
+			<div class="layui-row contentShadow full-height tran-bg">
+					<iframe id="ruleContent" name="ruleContent" src="${ctx}/workKnowledgeBase/pointRule/list" width="100%" height="100%" frameborder="0"></iframe>
+			</div>
+		</div>
+	</div>
+</div>
+	<script type="text/javascript">
+        function addDiyDom(treeId, treeNode) {
+            var spaceWidth = 15;
+            var switchObj = $("#" + treeNode.tId + "_switch"),
+                icoObj = $("#" + treeNode.tId + "_ico");
+            switchObj.remove();
+            icoObj.before(switchObj);
+
+            if (treeNode.level > 0) {
+                var spaceStr = "<span style='display: inline-block;width:" + (spaceWidth * treeNode.level)+ "px'></span>";
+                switchObj.before(spaceStr);
+            }
+        }
+
+        var setting = {
+            data:{
+                simpleData:{
+                    enable:true,
+                    idKey:"id",
+                    pIdKey:"pId",
+                    rootPId:'0'
+                }
+            },
+            view: {
+                showLine: false,
+                showIcon: true,
+                addDiyDom: addDiyDom
+            },
+            callback:{
+                onClick:function(event, treeId, treeNode){
+                    // 点击节点时,传递该节点ID及其所有子节点的规则
+                    var id = treeNode.id;
+                    $('#ruleContent').attr("src","${ctx}/workKnowledgeBase/pointRule/list?categoryId="+id);
+                }
+            }
+        };
+
+        function refreshTree(){
+            $.getJSON("${ctx}/workKnowledgeBase/pointCategory/treeData?"+ Math.random(),function(data){
+                $.fn.zTree.init($("#ztree"), setting, data).expandAll(true);
+            });
+        }
+        refreshTree();
+	</script>
+</body>
+</html>