Просмотр исходного кода

Merge branch 'master' of http://47.99.164.66:3000/hzdyjs-xg/all-project

李旭明 1 год назад
Родитель
Сommit
93711d76a3
20 измененных файлов с 714 добавлено и 331 удалено
  1. 2 2
      backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasRecordController.java
  2. 131 55
      backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java
  3. 13 13
      backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingRecordController.java
  4. 124 0
      backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsTrainingApplicationBo.java
  5. 14 0
      backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsTrainingApplicationMapper.java
  6. 1 1
      backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java
  7. 32 5
      backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java
  8. 80 17
      backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java
  9. 28 2
      backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApprovalServiceImpl.java
  10. 2 2
      backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasRecordMapper.xml
  11. 8 6
      backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingApplicationMapper.xml
  12. 22 20
      docs/需求文档/人事管理/培训管理需求.md
  13. 8 64
      web/src/api/rs/training.ts
  14. 49 2
      web/src/views/index.vue
  15. 1 1
      web/src/views/rs/index.vue
  16. 6 6
      web/src/views/rs/overseas/approval.vue
  17. 37 1
      web/src/views/rs/overseas/index.vue
  18. 2 0
      web/src/views/rs/overseasApproval/index.vue
  19. 131 128
      web/src/views/rs/training/index.vue
  20. 23 6
      web/src/views/rs/trainingApproval/index.vue

+ 2 - 2
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasRecordController.java

@@ -118,7 +118,7 @@ public class RsOverseasRecordController extends BaseController {
     /**
      * 基于申请信息创建出国记录
      */
-    @SaCheckPermission("rs:overseas:create")
+    @SaCheckPermission("rs:overseas:add")
     @Log(title = "创建出国记录", businessType = BusinessType.INSERT)
     @PostMapping("/create/{applicationId}")
     public R<Void> createFromApplication(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId) {
@@ -128,7 +128,7 @@ public class RsOverseasRecordController extends BaseController {
     /**
      * 更新出国记录完成状态
      */
-    @SaCheckPermission("rs:overseas:complete")
+    @SaCheckPermission("rs:overseas:edit")
     @Log(title = "更新出国记录完成状态", businessType = BusinessType.UPDATE)
     @PutMapping("/complete")
     public R<Void> updateCompletion(@RequestBody CompletionRequest request) {

+ 131 - 55
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java

@@ -23,6 +23,11 @@ import cn.dev33.satoken.annotation.SaIgnore;
 import cn.dev33.satoken.stp.StpUtil;
 import jakarta.servlet.http.HttpServletResponse;
 
+// 新增导入
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.service.ISysUserService;
+import org.dromara.common.core.utils.StringUtils;
+
 /**
  * 培训申请控制器
  * 
@@ -37,6 +42,10 @@ public class RsTrainingApplicationController extends BaseController {
     
     @Autowired
     private IRsTrainingApplicationService trainingApplicationService;
+    
+    // 新增用户服务注入
+    @Autowired
+    private ISysUserService sysUserService;
 
     /**
      * 查询培训申请列表
@@ -67,6 +76,114 @@ public class RsTrainingApplicationController extends BaseController {
         return R.ok(trainingApplicationService.selectRsTrainingApplicationById(applicationId));
     }
 
+    /**
+     * 获取当前登录用户的真实信息
+     * 
+     * @return 用户信息对象,包含用户ID和真实姓名
+     */
+    private UserInfo getCurrentUserInfo() {
+        try {
+            // 获取用户ID,处理可能的字符串格式
+            Object loginId = StpUtil.getLoginId();
+            Long userId = null;
+            
+            if (loginId != null) {
+                if (loginId instanceof Long) {
+                    userId = (Long) loginId;
+                } else if (loginId instanceof String) {
+                    String loginIdStr = (String) loginId;
+                    // 处理 "sys_user:1" 这种格式,提取数字部分
+                    if (loginIdStr.contains(":")) {
+                        String[] parts = loginIdStr.split(":");
+                        if (parts.length > 1) {
+                            try {
+                                userId = Long.parseLong(parts[1]);
+                            } catch (NumberFormatException e) {
+                                log.warn("无法解析用户ID字符串: {}", loginIdStr);
+                            }
+                        }
+                    } else {
+                        try {
+                            userId = Long.parseLong(loginIdStr);
+                        } catch (NumberFormatException e) {
+                            log.warn("无法解析用户ID字符串: {}", loginIdStr);
+                        }
+                    }
+                } else if (loginId instanceof Number) {
+                    userId = ((Number) loginId).longValue();
+                }
+            }
+            
+            if (userId != null) {
+                log.info("获取当前登录用户信息: userId={}", userId);
+                
+                // 从系统用户表获取用户真实信息
+                SysUserVo userVo = sysUserService.selectUserById(userId);
+                if (userVo != null) {
+                    String nickName = userVo.getNickName();
+                    String userName = userVo.getUserName();
+                    
+                    log.info("成功获取用户信息: userId={}, nickName={}, userName={}", userId, nickName, userName);
+                    
+                    // 使用用户名(根据前端要求)
+                    String displayName = StringUtils.isNotBlank(userName) ? userName : "用户" + userId;
+                    return new UserInfo(userId, displayName);
+                } else {
+                    log.warn("未找到用户信息: userId={}", userId);
+                }
+            } else {
+                log.warn("获取登录用户ID失败或格式不正确: loginId={}", loginId);
+            }
+        } catch (Exception e) {
+            log.error("获取用户信息异常: loginId={}, error={}", StpUtil.getLoginId(), e.getMessage(), e);
+        }
+        
+        // 如果获取失败,尝试从Sa-Token获取基本信息
+        try {
+            Object fallbackLoginId = StpUtil.getLoginId();
+            if (fallbackLoginId != null) {
+                log.warn("使用备用方案获取用户信息: loginId={}", fallbackLoginId);
+                // 尝试提取数字ID
+                if (fallbackLoginId instanceof String) {
+                    String loginIdStr = (String) fallbackLoginId;
+                    if (loginIdStr.contains(":")) {
+                        String[] parts = loginIdStr.split(":");
+                        if (parts.length > 1) {
+                            try {
+                                Long fallbackUserId = Long.parseLong(parts[1]);
+                                return new UserInfo(fallbackUserId, "用户" + fallbackUserId);
+                            } catch (NumberFormatException e) {
+                                log.warn("备用方案也无法解析用户ID: {}", loginIdStr);
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("备用方案也失败: {}", e.getMessage(), e);
+        }
+        
+        // 最后的备用方案
+        log.error("所有用户信息获取方案都失败,返回默认值");
+        return new UserInfo(1L, "系统用户");
+    }
+    
+    /**
+     * 用户信息内部类
+     */
+    private static class UserInfo {
+        private final Long userId;
+        private final String userName;
+        
+        public UserInfo(Long userId, String userName) {
+            this.userId = userId;
+            this.userName = StringUtils.isNotBlank(userName) ? userName : "用户" + userId;
+        }
+        
+        public Long getUserId() { return userId; }
+        public String getUserName() { return userName; }
+    }
+
     /**
      * 新增培训申请
      */
@@ -76,30 +193,10 @@ public class RsTrainingApplicationController extends BaseController {
     @PostMapping
     public R<Void> add(@Validated @RequestBody RsTrainingApplication application) {
         try {
-            // 尝试设置申请人为当前登录用户
-            String loginId = StpUtil.getLoginIdAsString();
-            if (loginId != null && loginId.startsWith("sys_user:")) {
-                // 从"sys_user:1"格式中提取数字部分
-                String[] parts = loginId.split(":");
-                if (parts.length > 1) {
-                    application.setUserId(Long.parseLong(parts[1]));
-                    application.setUserName("用户" + parts[1]); // 临时用户名
-                } else {
-                    // 如果无法解析,使用默认值
-                    application.setUserId(1L);
-                    application.setUserName("默认用户");
-                }
-            } else {
-                // 尝试正常转换
-                try {
-                    application.setUserId(StpUtil.getLoginIdAsLong());
-                    application.setUserName(loginId);
-                } catch (Exception e) {
-                    // 转换失败,使用默认值
-                    application.setUserId(1L);
-                    application.setUserName("默认用户");
-                }
-            }
+            // 获取当前登录用户的真实信息
+            UserInfo userInfo = getCurrentUserInfo();
+            application.setUserId(userInfo.getUserId());
+            application.setUserName(userInfo.getUserName());
             
             // 确保courseId的类型正确
             if (application.getCourseId() == null) {
@@ -144,7 +241,7 @@ public class RsTrainingApplicationController extends BaseController {
     /**
      * 取消培训申请
      */
-    @SaCheckPermission("rs:trainingapplication:edit")
+    @SaCheckPermission("rs:trainingapplication:cancel")
     @Log(title = "培训申请", businessType = BusinessType.UPDATE)
     @PutMapping("/cancel/{applicationId}")
     public R<Void> cancel(@PathVariable("applicationId") Long applicationId) {
@@ -154,7 +251,7 @@ public class RsTrainingApplicationController extends BaseController {
     /**
      * 查询我的培训申请列表
      */
-    @SaCheckPermission("rs:trainingapplication:query")
+    @SaCheckPermission("rs:trainingapplication:list")
     @GetMapping("/my")
     public TableDataInfo<RsTrainingApplication> listMyApplications(RsTrainingApplication application, PageQuery pageQuery) {
         return trainingApplicationService.selectPageMyApplications(StpUtil.getLoginIdAsLong(), application, pageQuery);
@@ -170,39 +267,18 @@ public class RsTrainingApplicationController extends BaseController {
     }
 
     /**
-     * 新增培训申请(无需权限验证,供普通用户使用)
+     * 新增培训申请(供普通用户使用)
      */
-    @SaIgnore
+    @SaCheckPermission("rs:trainingapplication:submit")
     @Log(title = "培训申请", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping("/submit")
     public R<Void> submit(@Validated @RequestBody RsTrainingApplication application) {
         try {
-            // 尝试设置申请人为当前登录用户,如果未登录则使用默认值
-            try {
-                String loginId = StpUtil.getLoginIdAsString();
-                if (loginId != null && loginId.startsWith("sys_user:")) {
-                    // 从"sys_user:1"格式中提取数字部分
-                    String[] parts = loginId.split(":");
-                    if (parts.length > 1) {
-                        application.setUserId(Long.parseLong(parts[1]));
-                        application.setUserName("用户" + parts[1]); // 临时用户名
-                    } else {
-                        // 如果无法解析,使用默认值
-                        application.setUserId(1L);
-                        application.setUserName("默认用户");
-                    }
-                } else {
-                    // 尝试正常转换
-                    application.setUserId(StpUtil.getLoginIdAsLong());
-                    application.setUserName(loginId);
-                }
-            } catch (Exception e) {
-                // 如果未登录或转换失败,使用默认值
-                application.setUserId(1L);
-                application.setUserName("默认用户");
-                log.info("用户未登录,使用默认用户信息");
-            }
+            // 获取当前登录用户的真实信息
+            UserInfo userInfo = getCurrentUserInfo();
+            application.setUserId(userInfo.getUserId());
+            application.setUserName(userInfo.getUserName());
             
             // 确保courseId的类型正确
             if (application.getCourseId() == null) {
@@ -226,7 +302,7 @@ public class RsTrainingApplicationController extends BaseController {
     /**
      * 更新培训申请状态
      */
-    @SaCheckPermission("rs:trainingapplication:edit")
+    @SaCheckPermission("rs:trainingapplication:process")
     @Log(title = "培训申请状态", businessType = BusinessType.UPDATE)
     @PutMapping("/status/{applicationId}/{status}")
     public R<Void> updateStatus(
@@ -239,7 +315,7 @@ public class RsTrainingApplicationController extends BaseController {
     /**
      * 重新提交被拒绝的培训申请
      */
-    @SaCheckPermission("rs:trainingapplication:edit")
+    @SaCheckPermission("rs:trainingapplication:submit")
     @Log(title = "重新提交培训申请", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
     @PutMapping("/resubmit/{applicationId}")

+ 13 - 13
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingRecordController.java

@@ -45,7 +45,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 查询培训记录列表
      */
-    @SaCheckPermission("rs:training:record:list")
+    @SaCheckPermission("rs:trainingrecord:list")
     @GetMapping("/list")
     public TableDataInfo<RsTrainingRecord> list(RsTrainingRecord record, PageQuery pageQuery) {
         return trainingRecordService.selectPageRsTrainingRecordList(record, pageQuery);
@@ -54,7 +54,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 导出培训记录列表
      */
-    @SaCheckPermission("rs:training:record:export")
+    @SaCheckPermission("rs:trainingrecord:export")
     @Log(title = "培训记录", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
     public void export(HttpServletResponse response, RsTrainingRecord record) {
@@ -65,7 +65,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 获取培训记录详细信息
      */
-    @SaCheckPermission("rs:training:record:query")
+    @SaCheckPermission("rs:trainingrecord:query")
     @GetMapping("/{recordId}")
     public R<RsTrainingRecord> getInfo(@PathVariable("recordId") Long recordId) {
         return R.ok(trainingRecordService.selectRsTrainingRecordById(recordId));
@@ -74,7 +74,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 新增培训记录
      */
-    @SaCheckPermission("rs:training:record:add")
+    @SaCheckPermission("rs:trainingrecord:add")
     @Log(title = "培训记录", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping
@@ -85,7 +85,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 修改培训记录
      */
-    @SaCheckPermission("rs:training:record:edit")
+    @SaCheckPermission("rs:trainingrecord:edit")
     @Log(title = "培训记录", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
     @PutMapping
@@ -96,7 +96,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 删除培训记录
      */
-    @SaCheckPermission("rs:training:record:remove")
+    @SaCheckPermission("rs:trainingrecord:remove")
     @Log(title = "培训记录", businessType = BusinessType.DELETE)
     @DeleteMapping("/{recordIds}")
     public R<Void> remove(@PathVariable Long[] recordIds) {
@@ -106,7 +106,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 修复培训记录中缺失的课程信息
      */
-    @SaCheckPermission("rs:training:record:edit")
+    @SaCheckPermission("rs:trainingrecord:edit")
     @Log(title = "培训记录", businessType = BusinessType.UPDATE)
     @PostMapping("/fixCourseInfo")
     public R<Integer> fixCourseInfo() {
@@ -118,7 +118,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 查询我的培训记录列表
      */
-    @SaCheckPermission("rs:training:record:query")
+    @SaCheckPermission("rs:trainingrecord:query")
     @GetMapping("/my")
     public TableDataInfo<RsTrainingRecord> listMyRecords(RsTrainingRecord record, PageQuery pageQuery) {
         return trainingRecordService.selectPageMyRecords(StpUtil.getLoginIdAsLong(), record, pageQuery);
@@ -127,7 +127,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 更新培训记录完成状态
      */
-    @SaCheckPermission("rs:training:record:edit")
+    @SaCheckPermission("rs:trainingrecord:edit")
     @Log(title = "培训记录", businessType = BusinessType.UPDATE)
     @PutMapping("/status/{recordId}/{status}")
     public R<Void> updateCompletionStatus(@PathVariable("recordId") Long recordId, @PathVariable("status") String status) {
@@ -137,7 +137,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 上传培训证书
      */
-    @SaCheckPermission("rs:training:record:edit")
+    @SaCheckPermission("rs:trainingrecord:edit")
     @Log(title = "培训记录", businessType = BusinessType.UPDATE)
     @PutMapping("/certificate/{recordId}")
     public R<Void> uploadCertificate(@PathVariable("recordId") Long recordId, @RequestBody Map<String, String> params) {
@@ -149,7 +149,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 上传培训证书文件
      */
-    @SaCheckPermission("rs:training:record:edit")
+    @SaCheckPermission("rs:trainingrecord:edit")
     @Log(title = "上传培训证书文件", businessType = BusinessType.UPDATE)
     @PostMapping("/certificate/upload/{recordId}")
     public R<Map<String, String>> uploadCertificateFile(@PathVariable Long recordId, @RequestParam("file") MultipartFile file) throws IOException {
@@ -196,7 +196,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 获取培训统计数据
      */
-    @SaCheckPermission("rs:training:record:list")
+    @SaCheckPermission("rs:trainingrecord:list")
     @GetMapping("/statistics")
     public R<Map<String, Object>> getStatistics() {
         // 这里可以实现培训统计数据的获取逻辑
@@ -207,7 +207,7 @@ public class RsTrainingRecordController extends BaseController {
     /**
      * 更新培训记录的证书附件ID
      */
-    @SaCheckPermission("rs:training:record:edit")
+    @SaCheckPermission("rs:trainingrecord:edit")
     @Log(title = "更新培训记录证书附件", businessType = BusinessType.UPDATE)
     @PutMapping("/attachments/{recordId}")
     public R<Void> updateAttachments(@PathVariable Long recordId, @RequestBody Map<String, String> params) {

+ 124 - 0
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsTrainingApplicationBo.java

@@ -0,0 +1,124 @@
+package org.dromara.rs.domain.bo;
+
+import org.dromara.rs.domain.RsTrainingApplication;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Min;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.util.Date;
+
+/**
+ * 培训申请业务对象 rs_training_application
+ *
+ * @author weizzzz
+ * @date 2025-07-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = RsTrainingApplication.class, reverseConvertGenerate = false)
+public class RsTrainingApplicationBo extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 申请ID
+     */
+    @NotNull(message = "申请ID不能为空", groups = { EditGroup.class })
+    private Long applicationId;
+
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+
+    /**
+     * 培训名称
+     */
+    @NotBlank(message = "培训名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String courseName;
+
+    /**
+     * 申请人ID
+     */
+    @NotNull(message = "申请人ID不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long userId;
+
+    /**
+     * 申请人姓名
+     */
+    @NotBlank(message = "申请人姓名不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String userName;
+
+    /**
+     * 所属部门
+     */
+    private String department;
+
+    /**
+     * 职位
+     */
+    private String position;
+
+    /**
+     * 申请理由
+     */
+    @NotBlank(message = "申请理由不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String reason;
+
+    /**
+     * 状态(0待审核 1已通过 2已拒绝 3进行中 4已完成)
+     */
+    private String status;
+
+    /**
+     * 申请时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date applyTime;
+
+    /**
+     * 当前审批人
+     */
+    private String currentApprover;
+    
+    /**
+     * 培训地点
+     */
+    private String location;
+    
+    /**
+     * 学时
+     */
+    @Min(value = 1, message = "学时不能小于1", groups = { AddGroup.class, EditGroup.class })
+    private Integer duration;
+    
+    /**
+     * 开始时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+    
+    /**
+     * 结束时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+    
+    /**
+     * 拒绝时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date rejectTime;
+} 

+ 14 - 0
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsTrainingApplicationMapper.java

@@ -2,6 +2,8 @@ package org.dromara.rs.mapper;
 
 import org.dromara.rs.domain.RsTrainingApplication;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
@@ -20,6 +22,10 @@ public interface RsTrainingApplicationMapper extends BaseMapperPlus<RsTrainingAp
      * @param application 培训申请
      * @return 培训申请集合
      */
+    @DataPermission({
+        @DataColumn(key = "deptName", value = "create_dept"),
+        @DataColumn(key = "userName", value = "create_by")
+    })
     List<RsTrainingApplication> selectRsTrainingApplicationList(RsTrainingApplication application);
     
     /**
@@ -30,6 +36,10 @@ public interface RsTrainingApplicationMapper extends BaseMapperPlus<RsTrainingAp
      * @param limit 限制数量
      * @return 培训申请集合
      */
+    @DataPermission({
+        @DataColumn(key = "deptName", value = "create_dept"),
+        @DataColumn(key = "userName", value = "create_by")
+    })
     List<RsTrainingApplication> selectRsTrainingApplicationListPage(@Param("app") RsTrainingApplication app, @Param("offset") long offset, @Param("limit") long limit);
     
     /**
@@ -38,5 +48,9 @@ public interface RsTrainingApplicationMapper extends BaseMapperPlus<RsTrainingAp
      * @param applicationId 培训申请ID
      * @return 培训申请
      */
+    @DataPermission({
+        @DataColumn(key = "deptName", value = "create_dept"),
+        @DataColumn(key = "userName", value = "create_by")
+    })
     RsTrainingApplication selectRsTrainingApplicationById(Long applicationId);
 } 

+ 1 - 1
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java

@@ -276,7 +276,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS
         
         // 手动设置多租户和删除标志字段
         if (approval.getTenantId() == null || approval.getTenantId().isEmpty()) {
-            approval.setTenantId("000000");
+            approval.setTenantId(LoginHelper.getTenantId());
         }
         if (approval.getDeleted() == null || approval.getDeleted().isEmpty()) {
             approval.setDeleted("0");

+ 32 - 5
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java

@@ -8,12 +8,14 @@ import java.util.List;
 
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import cn.dev33.satoken.stp.StpUtil;
 import org.dromara.rs.mapper.RsOverseasApprovalMapper;
 import org.dromara.rs.domain.RsOverseasApproval;
 import org.dromara.rs.domain.bo.RsOverseasApprovalBo;
@@ -146,18 +148,43 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService
      */
     private LambdaQueryWrapper<RsOverseasApproval> buildQueryWrapper(RsOverseasApprovalQuery query) {
         LambdaQueryWrapper<RsOverseasApproval> lqw = Wrappers.lambdaQuery();
+        
+        // ===== 业务逻辑层权限过滤 =====
+        // 根据用户角色进行数据权限过滤
+        if (!LoginHelper.isSuperAdmin()) {
+            // 获取当前登录用户信息
+            Long currentUserId = LoginHelper.getUserId();
+            
+            // 检查用户角色并应用相应的数据过滤
+            if (StpUtil.hasRole("rs-Applicant")) {
+                // 申请人只能看到与自己相关的审批记录
+                lqw.exists("SELECT 1 FROM rs_overseas_application app WHERE app.application_id = rs_overseas_approval.application_id AND app.applicant_id = " + currentUserId);
+            } else if (StpUtil.hasRole("rs-admin")) {
+                // 人事管理员可以看到所有审批记录
+                // 不设置额外过滤条件,让管理员能看到所有审批
+            } else {
+                // 默认情况:只能看到自己创建或处理的审批
+                lqw.and(wrapper -> wrapper.eq(RsOverseasApproval::getCreateBy, currentUserId)
+                    .or().eq(RsOverseasApproval::getApproverId, currentUserId));
+            }
+        }
+        
+        // 添加租户过滤
+        lqw.eq(RsOverseasApproval::getTenantId, LoginHelper.getTenantId());
+        
         lqw.eq(query.getApplicationId() != null, RsOverseasApproval::getApplicationId, query.getApplicationId());
         lqw.eq(query.getApproverId() != null, RsOverseasApproval::getApproverId, query.getApproverId());
         lqw.like(StringUtils.isNotBlank(query.getApproverName()), RsOverseasApproval::getApproverName, query.getApproverName());
         lqw.eq(StringUtils.isNotBlank(query.getApprovalLevel()), RsOverseasApproval::getApprovalLevel, query.getApprovalLevel());
         
-        // 如果指定了审批状态,使用指定的状态;否则只查询已完成审批的记录(排除待审核状态)
+        // 如果指定了审批状态,使用指定的状态;对于管理员,显示所有状态
         if (StringUtils.isNotBlank(query.getApprovalStatus())) {
             lqw.eq(RsOverseasApproval::getApprovalStatus, query.getApprovalStatus());
-        } else {
-            // 默认只查询已完成审批的记录:1-通过,2-拒绝(排除0-待审核)
+        } else if (!LoginHelper.isSuperAdmin() && !StpUtil.hasRole("rs-admin")) {
+            // 非管理员默认只查询已完成审批的记录:1-通过,2-拒绝(排除0-待审核)
             lqw.in(RsOverseasApproval::getApprovalStatus, "1", "2");
         }
+        // 管理员和超级管理员可以看到所有状态的审批记录
         
         lqw.ge(query.getApprovalTimeBegin() != null, RsOverseasApproval::getApprovalTime, query.getApprovalTimeBegin());
         lqw.le(query.getApprovalTimeEnd() != null, RsOverseasApproval::getApprovalTime, query.getApprovalTimeEnd());
@@ -220,7 +247,7 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService
         try {
             // 1. 清理现有测试数据(谨慎操作,只清理测试租户数据)
             baseMapper.delete(new LambdaQueryWrapper<RsOverseasApproval>()
-                    .eq(RsOverseasApproval::getTenantId, "000000")
+                    .eq(RsOverseasApproval::getTenantId, LoginHelper.getTenantId())
                     .eq(RsOverseasApproval::getDeleted, "0"));
 
             // 2. 创建并插入标准测试数据
@@ -265,7 +292,7 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService
             approval.setApprovalStatus(approvalStatuses[i]);
             approval.setApprovalOpinion(opinions[i]);
             approval.setApprovalTime(Date.from(LocalDateTime.now().minusDays(i + 1).atZone(ZoneId.systemDefault()).toInstant()));
-            approval.setTenantId("000000");
+            approval.setTenantId(LoginHelper.getTenantId());
             approval.setDeleted("0");
             approval.setCreateBy(1L);
             approval.setCreateTime(Date.from(LocalDateTime.now().minusDays(i + 1).atZone(ZoneId.systemDefault()).toInstant()));

+ 80 - 17
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java

@@ -1,5 +1,6 @@
 package org.dromara.rs.service.impl;
 
+import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -10,9 +11,10 @@ import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.rs.domain.RsTrainingApplication;
 import org.dromara.rs.domain.RsTrainingRecord;
-import org.dromara.rs.domain.bo.RsTrainingApplicationBo;
+// import org.dromara.rs.domain.bo.RsTrainingApplicationBo;
 
 import java.util.Arrays;
 
@@ -62,9 +64,30 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
         // 计算偏移量
         long offset = (current - 1) * size;
 
-        // 设置租户ID
+        // 设置租户ID - 使用当前登录用户的租户ID
         if (application.getTenantId() == null || application.getTenantId().isEmpty()) {
-            application.setTenantId("000000");
+            application.setTenantId(LoginHelper.getTenantId());
+        }
+        
+        // ===== 业务逻辑层权限过滤 =====
+        // 根据用户角色进行数据权限过滤
+        if (!LoginHelper.isSuperAdmin()) {
+            // 获取当前登录用户信息
+            Long currentUserId = LoginHelper.getUserId();
+            Long currentDeptId = LoginHelper.getDeptId();
+            String username = LoginHelper.getUsername();
+            
+            // 检查用户角色并应用相应的数据过滤
+            if (StpUtil.hasRole("rs-Applicant")) {
+                // 申请人只能看到自己的申请
+                application.setUserId(currentUserId);
+            } else if (StpUtil.hasRole("rs-admin")) {
+                // 人事管理员可以看到所有申请(包括审批和部门管理功能)
+                // 不设置额外过滤条件,让管理员能看到所有申请
+            } else {
+                // 默认情况:只能看到自己创建的申请
+                application.setCreateBy(currentUserId);
+            }
         }
         
         // 查询总数
@@ -95,6 +118,26 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
      */
     @Override
     public List<RsTrainingApplication> selectRsTrainingApplicationList(RsTrainingApplication application) {
+        // ===== 业务逻辑层权限过滤 =====
+        // 根据用户角色进行数据权限过滤
+        if (!LoginHelper.isSuperAdmin()) {
+            // 获取当前登录用户信息
+            Long currentUserId = LoginHelper.getUserId();
+            Long currentDeptId = LoginHelper.getDeptId();
+            
+            // 检查用户角色并应用相应的数据过滤
+            if (StpUtil.hasRole("rs-Applicant")) {
+                // 申请人只能看到自己的申请
+                application.setUserId(currentUserId);
+            } else if (StpUtil.hasRole("rs-admin")) {
+                // 人事管理员可以看到所有申请(包括审批和部门管理功能)
+                // 不设置额外过滤条件,让管理员能看到所有申请
+            } else {
+                // 默认情况:只能看到自己创建的申请
+                application.setCreateBy(currentUserId);
+            }
+        }
+        
         // 使用自定义的Mapper方法查询,已经关联了课程表获取课程名称
         List<RsTrainingApplication> list = trainingApplicationMapper.selectRsTrainingApplicationList(application);
         
@@ -143,9 +186,10 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
      */
     private LambdaQueryWrapper<RsTrainingApplication> buildQueryWrapper(RsTrainingApplication application) {
         LambdaQueryWrapper<RsTrainingApplication> lqw = new LambdaQueryWrapper<>();
-        // 排除已审批完成的数据:1-已通过,3-进行中,4-已完成
-        // 只保留:0-待审核,2-已拒绝
-        lqw.notIn(RsTrainingApplication::getStatus, "1", "3", "4");
+        // 移除硬编码状态过滤,让管理员能看到所有状态的申请
+        // 原注释:排除已审批完成的数据:1-已通过,3-进行中,4-已完成
+        // 原注释:只保留:0-待审核,2-已拒绝
+        // 修复:移除 lqw.notIn(RsTrainingApplication::getStatus, "1", "3", "4") 限制
         lqw.like(application.getUserName() != null, RsTrainingApplication::getUserName, application.getUserName());
         lqw.eq(application.getCourseId() != null, RsTrainingApplication::getCourseId, application.getCourseId());
         lqw.eq(application.getUserId() != null, RsTrainingApplication::getUserId, application.getUserId());
@@ -166,6 +210,34 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
         // 使用自定义的Mapper方法查询,已经关联了课程表获取课程名称
         RsTrainingApplication application = trainingApplicationMapper.selectRsTrainingApplicationById(applicationId);
         
+        // ===== 业务逻辑层权限过滤 =====
+        // 检查当前用户是否有权限查看此申请
+        if (application != null && !LoginHelper.isSuperAdmin()) {
+            Long currentUserId = LoginHelper.getUserId();
+            Long currentDeptId = LoginHelper.getDeptId();
+            
+            boolean hasPermission = false;
+            
+            // 检查用户角色权限
+            if (StpUtil.hasRole("rs-Applicant")) {
+                // 申请人只能查看自己的申请
+                hasPermission = application.getUserId().equals(currentUserId);
+            } else if (StpUtil.hasRole("rs-admin")) {
+                // 人事管理员可以查看所有申请
+                hasPermission = true;
+            } else {
+                // 默认情况:只能查看自己创建的申请
+                hasPermission = application.getCreateBy() != null && 
+                               application.getCreateBy().equals(currentUserId);
+            }
+            
+            // 如果没有权限,返回null
+            if (!hasPermission) {
+                log.warn("用户 {} 尝试访问无权限的培训申请: {}", currentUserId, applicationId);
+                return null;
+            }
+        }
+        
         // 处理自定义培训课程(courseId=0)的情况
         if (application != null && application.getCourseId() != null && application.getCourseId() == 0 
             && (application.getCourseName() == null || application.getCourseName().isEmpty())) {
@@ -189,15 +261,6 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
                 application.setApplicationId(System.currentTimeMillis());
             }
             
-            // 设置默认值,防止空指针异常
-            if (application.getUserId() == null) {
-                application.setUserId(1L); // 默认用户ID
-            }
-            
-            if (application.getUserName() == null || application.getUserName().isEmpty()) {
-                application.setUserName("默认用户");
-            }
-            
             // 处理自定义培训课程的情况(courseId = 0 或 null)
             // 确保courseName不为空
             if (application.getCourseName() == null || application.getCourseName().isEmpty()) {
@@ -207,7 +270,7 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
             application.setCreateTime(now);
             application.setApplyTime(now);
             application.setStatus("0"); // 待审核状态
-            application.setTenantId("000000"); // 设置默认租户ID
+            application.setTenantId(LoginHelper.getTenantId()); // 设置当前用户租户ID
             return trainingApplicationMapper.insert(application);
             
             // 以上代码已处理所有情况
@@ -420,7 +483,7 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
             record.setUpdateTime(now);
             
             // 设置租户和用户信息
-            record.setTenantId(application.getTenantId() != null ? application.getTenantId() : "000000");
+            record.setTenantId(application.getTenantId() != null ? application.getTenantId() : LoginHelper.getTenantId());
             record.setCreateBy(application.getUserId()); // 创建者设为申请人
             record.setUpdateBy(application.getUserId()); // 更新者设为申请人
             record.setCreateDept(application.getCreateDept() != null ? application.getCreateDept() : 1L); // 使用createDept,如果没有则设为默认值

+ 28 - 2
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApprovalServiceImpl.java

@@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import cn.dev33.satoken.stp.StpUtil;
 import org.dromara.rs.mapper.RsTrainingApprovalMapper;
 import org.dromara.rs.mapper.RsTrainingApplicationMapper;
 import org.dromara.rs.domain.RsTrainingApproval;
@@ -65,6 +66,31 @@ public class RsTrainingApprovalServiceImpl implements IRsTrainingApprovalService
      */
     private LambdaQueryWrapper<RsTrainingApproval> buildQueryWrapper(RsTrainingApproval approval) {
         LambdaQueryWrapper<RsTrainingApproval> lqw = new LambdaQueryWrapper<>();
+        
+        // ===== 业务逻辑层权限过滤 =====
+        // 根据用户角色进行数据权限过滤
+        if (!LoginHelper.isSuperAdmin()) {
+            // 获取当前登录用户信息
+            Long currentUserId = LoginHelper.getUserId();
+            
+            // 检查用户角色并应用相应的数据过滤
+            if (StpUtil.hasRole("rs-Applicant")) {
+                // 申请人只能看到与自己相关的审批记录
+                // 这里需要通过申请表关联查询
+                lqw.exists("SELECT 1 FROM rs_training_application app WHERE app.application_id = rs_training_approval.application_id AND app.user_id = " + currentUserId);
+            } else if (StpUtil.hasRole("rs-admin")) {
+                // 人事管理员可以看到所有审批记录
+                // 不设置额外过滤条件,让管理员能看到所有审批
+            } else {
+                // 默认情况:只能看到自己创建或处理的审批
+                lqw.and(wrapper -> wrapper.eq(RsTrainingApproval::getCreateBy, currentUserId)
+                    .or().eq(RsTrainingApproval::getApproverId, currentUserId));
+            }
+        }
+        
+        // 添加租户过滤
+        lqw.eq(RsTrainingApproval::getTenantId, LoginHelper.getTenantId());
+        
         lqw.eq(approval.getApplicationId() != null, RsTrainingApproval::getApplicationId, approval.getApplicationId());
         lqw.eq(approval.getApproverId() != null, RsTrainingApproval::getApproverId, approval.getApproverId());
         lqw.eq(approval.getApprovalLevel() != null, RsTrainingApproval::getApprovalLevel, approval.getApprovalLevel());
@@ -158,9 +184,9 @@ public class RsTrainingApprovalServiceImpl implements IRsTrainingApprovalService
         // 计算偏移量
         long offset = (current - 1) * size;
 
-        // 设置租户ID
+        // 设置租户ID - 使用当前登录用户的租户ID
         if (approval.getTenantId() == null || approval.getTenantId().isEmpty()) {
-            approval.setTenantId("000000");
+            approval.setTenantId(LoginHelper.getTenantId());
         }
         
         // 查询总数

+ 2 - 2
backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasRecordMapper.xml

@@ -88,9 +88,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             select 1 from rs_overseas_record ors 
             where ors.application_id = oa.application_id 
             and ors.deleted = '0'
-            and ors.tenant_id = '000000'
+            and ors.tenant_id = #{tenantId}
         )
-        and oa.tenant_id = '000000'
+        and oa.tenant_id = #{tenantId}
     </sql>
 
     <select id="selectOverseasRecordList" parameterType="org.dromara.rs.domain.query.RsOverseasRecordQuery" resultMap="RsOverseasRecordVoResult">

+ 8 - 6
backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingApplicationMapper.xml

@@ -45,9 +45,10 @@
     <select id="selectRsTrainingApplicationList" parameterType="org.dromara.rs.domain.RsTrainingApplication" resultMap="RsTrainingApplicationResult">
         <include refid="selectRsTrainingApplicationVo"/>
         <where>
-            <!-- 排除已审批完成的数据:1-已通过,3-进行中,4-已完成 -->
-            <!-- 只保留:0-待审核,2-已拒绝 -->
-            AND a.status NOT IN ('1', '3', '4')
+            <!-- 移除硬编码状态过滤,让管理员能看到所有状态的申请 -->
+            <!-- 原注释:排除已审批完成的数据:1-已通过,3-进行中,4-已完成 -->
+            <!-- 原注释:只保留:0-待审核,2-已拒绝 -->
+            <!-- 修复:移除 AND a.status NOT IN ('1', '3', '4') 限制 -->
             <if test="courseId != null ">and a.course_id = #{courseId}</if>
             <if test="userId != null ">and a.user_id = #{userId}</if>
             <if test="userName != null  and userName != ''">and a.user_name like concat('%', #{userName}, '%')</if>
@@ -66,9 +67,10 @@
     <select id="selectRsTrainingApplicationListPage" resultMap="RsTrainingApplicationResult">
         <include refid="selectRsTrainingApplicationVo"/>
         <where>
-            <!-- 排除已审批完成的数据:1-已通过,3-进行中,4-已完成 -->
-            <!-- 只保留:0-待审核,2-已拒绝 -->
-            AND a.status NOT IN ('1', '3', '4')
+            <!-- 移除硬编码状态过滤,让管理员能看到所有状态的申请 -->
+            <!-- 原注释:排除已审批完成的数据:1-已通过,3-进行中,4-已完成 -->
+            <!-- 原注释:只保留:0-待审核,2-已拒绝 -->
+            <!-- 修复:移除 AND a.status NOT IN ('1', '3', '4') 限制 -->
             <if test="app.courseId != null ">and a.course_id = #{app.courseId}</if>
             <if test="app.userId != null ">and a.user_id = #{app.userId}</if>
             <if test="app.userName != null and app.userName != ''">and a.user_name like concat('%', #{app.userName}, '%')</if>

+ 22 - 20
docs/需求文档/人事管理/培训管理需求.md

@@ -233,14 +233,16 @@ CREATE TABLE `rs_training_record` (
 
 ## 四、前端页面设计
 
-### 1. 培训课程管理页面
+### 1. 培训课程管理页面 ⚠️【已废弃 - 2025-01-03】
 
-- **页面路径**:/rs/training/course
-- **功能组件**:
-  - 搜索表单(课程名称、类别、状态)
-  - 操作按钮(新增、修改、删除、导出)
-  - 数据表格(课程信息展示)
-  - 表单对话框(新增/修改课程信息)
+**重要说明**:培训课程模块已废弃,课程信息现在直接存储在 `rs_training_application` 表中,通过培训申请功能管理。
+
+~~- **页面路径**:/rs/training/course~~
+~~- **功能组件**:~~
+  ~~- 搜索表单(课程名称、类别、状态)~~
+  ~~- 操作按钮(新增、修改、删除、导出)~~
+  ~~- 数据表格(课程信息展示)~~
+  ~~- 表单对话框(新增/修改课程信息)~~
 
 ### 2. 培训申请管理页面
 
@@ -276,12 +278,12 @@ CREATE TABLE `rs_training_record` (
 ### 1. 权限项定义
 ```
 # 培训课程管理权限
-- rs:training:course:list    查看培训课程列表
-- rs:training:course:query   查询培训课程详情
-- rs:training:course:add     新增培训课程
-- rs:training:course:edit    修改培训课程
-- rs:training:course:remove  删除培训课程
-- rs:training:course:export  导出培训课程
+~~- rs:training:course:list    查看培训课程列表~~ 【已废弃】
+~~- rs:training:course:query   查询培训课程详情~~ 【已废弃】
+~~- rs:training:course:add     新增培训课程~~ 【已废弃】
+~~- rs:training:course:edit    修改培训课程~~ 【已废弃】
+~~- rs:training:course:remove  删除培训课程~~ 【已废弃】
+~~- rs:training:course:export  导出培训课程~~ 【已废弃】
 
 # 培训申请管理权限
 - rs:training:application:list    查看培训申请列表
@@ -295,11 +297,11 @@ CREATE TABLE `rs_training_record` (
 - rs:training:approval:process  处理培训审批
 
 # 培训记录管理权限
-- rs:training:record:list    查看培训记录列表
-- rs:training:record:query   查询培训记录详情
-- rs:training:record:add     新增培训记录
-- rs:training:record:edit    修改培训记录
-- rs:training:record:export  导出培训记录
+- rs:trainingrecord:list    查看培训记录列表
+- rs:trainingrecord:query   查询培训记录详情
+- rs:trainingrecord:add     新增培训记录
+- rs:trainingrecord:edit    修改培训记录
+- rs:trainingrecord:export  导出培训记录
 ```
 
 ### 2. 角色权限分配
@@ -310,14 +312,14 @@ CREATE TABLE `rs_training_record` (
 - rs:training:course:query
 - rs:training:application:add
 - rs:training:application:query
-- rs:training:record:query (个人)
+- rs:trainingrecord:query (个人)
 
 # 培训管理员角色 (TRAINING_ADMIN)
 - rs:training:course:*
 - rs:training:application:list
 - rs:training:application:query
 - rs:training:application:export
-- rs:training:record:*
+- rs:trainingrecord:*
 
 # 审批领导角色 (APPROVER)
 - rs:training:course:list

+ 8 - 64
web/src/api/rs/training.ts

@@ -56,6 +56,7 @@ export interface ApplicationQuery {
   userName?: string;
   department?: string;
   status?: string;
+  excludeStatus?: string; // 排除指定状态的数据
   params?: Record<string, any>;
 }
 
@@ -110,6 +111,7 @@ export interface RecordQuery {
   location?: string;
   department?: string;
   completionStatus?: string;
+  includeApprovedApplications?: boolean; // 是否包含已通过审批的申请数据
   params?: Record<string, any>;
 }
 
@@ -158,66 +160,11 @@ export interface RecordForm {
   remark?: string;
 }
 
-// 查询培训课程列表
-export function listTrainingCourse(query: CourseQuery) {
-  return request({
-    url: '/rs/training/course/list',
-    method: 'get',
-    params: query
-  });
-}
-
-// 获取培训课程详情
-export function getTrainingCourse(courseId: string | number) {
-  return request({
-    url: `/rs/training/course/${courseId}`,
-    method: 'get'
-  });
-}
-
-// 新增培训课程
-export function addTrainingCourse(data: CourseForm) {
-  return request({
-    url: '/rs/training/course',
-    method: 'post',
-    data: data
-  });
-}
-
-// 修改培训课程
-export function updateTrainingCourse(data: CourseForm) {
-  return request({
-    url: '/rs/training/course',
-    method: 'put',
-    data: data
-  });
-}
-
-// 删除培训课程
-export function delTrainingCourse(courseId: string | number | Array<string | number>) {
-  return request({
-    url: `/rs/training/course/${typeof courseId === 'object' ? courseId.join(',') : courseId}`,
-    method: 'delete'
-  });
-}
-
-// 切换培训课程状态
-export function toggleCourseStatus(courseId: string | number) {
-  return request({
-    url: `/rs/training/course/toggle/${courseId}`,
-    method: 'put'
-  });
-}
-
-// 导出培训课程
-export function exportTrainingCourse(query: CourseQuery) {
-  return request({
-    url: '/rs/training/course/export',
-    method: 'get',
-    params: query,
-    responseType: 'blob'
-  });
-}
+// ============================================
+// 注意:培训课程模块已废弃 (2025-01-03)
+// 课程信息现在直接存储在 rs_training_application 表中
+// 以下培训课程相关接口已移除,请使用培训申请相关接口
+// ============================================
 
 // 查询培训申请列表
 export function listTrainingApplication(query: ApplicationQuery) {
@@ -356,13 +303,10 @@ export function exportTrainingRecord(query: RecordQuery) {
   });
 }
 
-// 提交培训申请(无需权限验证,供普通用户使用)
+// 提交培训申请(需要权限验证)
 export function submitTrainingApplication(data: ApplicationForm) {
   return request({
     url: '/rs/training/application/submit',
-    headers: {
-      isToken: false
-    },
     method: 'post',
     data: data
   });

+ 49 - 2
web/src/views/index.vue

@@ -304,9 +304,21 @@ const flattenMenus = (menus: any[], parentChain: string[] = [], parentPath = '',
   menus.forEach((menu, menuIndex) => {
     // 跳过隐藏的菜单
     if (menu.hidden === true) {
+      console.log(`跳过隐藏菜单: ${menu.meta?.title || menu.name}`);
       return;
     }
 
+    // 添加调试信息,特别关注工作流菜单
+    if (menu.meta?.title === '工作流' || menu.name === 'Workflow11616') {
+      console.log('=== 工作流菜单处理开始 ===');
+      console.log('工作流菜单原始数据:', menu);
+      console.log('工作流菜单组件类型:', menu.component);
+      console.log('工作流菜单是否有子菜单:', !!menu.children);
+      console.log('工作流菜单子菜单数量:', menu.children?.length || 0);
+      console.log('工作流菜单层级:', level);
+      console.log('工作流菜单父路径:', parentPath);
+    }
+
     // 构建完整路径 - 移到外层,确保在递归调用中也能访问
     let fullPath = menu.path;
 
@@ -363,8 +375,40 @@ const flattenMenus = (menus: any[], parentChain: string[] = [], parentPath = '',
       // 检查是否有可见的子菜单
       const hasVisibleChildren = menu.children && menu.children.some((child: any) => !child.hidden);
 
-      // 只有叶子节点(没有子菜单或子菜单都是隐藏的)才作为可点击的菜单项
-      if (!hasVisibleChildren && fullPath && fullPath !== '/') {
+      // 检查是否为目录类型菜单
+      // 1. 组件为Layout或ParentView字符串
+      // 2. 组件为Vue对象且没有子菜单(可能是目录)
+      // 3. menuType为'M'(目录类型)
+      const isDirectoryMenu = menu.component === 'Layout' ||
+                             menu.component === 'ParentView' ||
+                             (typeof menu.component === 'object' && !hasVisibleChildren) ||
+                             menu.menuType === 'M';
+
+      // 添加工作流菜单的详细调试信息
+      if (menu.meta?.title === '工作流' || menu.name === 'Workflow11616') {
+        console.log('=== 工作流菜单判断过程 ===');
+        console.log('hasVisibleChildren:', hasVisibleChildren);
+        console.log('isDirectoryMenu:', isDirectoryMenu);
+        console.log('menu.component:', menu.component);
+        console.log('menu.component类型:', typeof menu.component);
+        console.log('menu.menuType:', menu.menuType);
+        console.log('menu.children:', menu.children);
+        console.log('fullPath:', fullPath);
+        console.log('level:', level);
+        console.log('是否应该创建菜单项:', !hasVisibleChildren && !isDirectoryMenu && fullPath && fullPath !== '/');
+        console.log('完整菜单对象:', JSON.stringify(menu, null, 2));
+      }
+
+      // 目录菜单处理逻辑:如果是目录菜单且没有子菜单,直接跳过
+      if (isDirectoryMenu && !hasVisibleChildren) {
+        if (menu.meta?.title === '工作流' || menu.name === 'Workflow11616') {
+          console.log('=== 工作流菜单被跳过 ===');
+          console.log('原因: 是目录菜单且没有子菜单');
+        }
+        // 不创建菜单项,直接跳过
+      }
+      // 只有叶子节点(没有子菜单或子菜单都是隐藏的)且不是目录菜单才作为可点击的菜单项
+      else if (!hasVisibleChildren && !isDirectoryMenu && fullPath && fullPath !== '/') {
         const menuAction: MenuAction = {
           id: `menu-${menu.name || fullPath}`,
           title: menu.meta.title,
@@ -389,6 +433,9 @@ const flattenMenus = (menus: any[], parentChain: string[] = [], parentPath = '',
         );
 
         result.push(menuAction);
+      } else if (menu.meta?.title === '工作流' || menu.name === 'Workflow11616') {
+        console.log('=== 工作流菜单被过滤掉 ===');
+        console.log('原因: 是目录菜单或没有有效路径');
       }
     }
 

+ 1 - 1
web/src/views/rs/index.vue

@@ -230,7 +230,7 @@
                     <el-button icon="Download" @click="handleExportApplications" v-hasPermi="['rs:application:export']" style="display: none">导出</el-button>
                   </el-col>
                   <el-col :span="1.5">
-                    <el-button icon="Printer" @click="handlePrintApplications">打印</el-button>
+                    <el-button icon="Printer" @click="handlePrintApplications" style="display: none">打印</el-button>
                   </el-col>
                   <el-col :span="1.5">
                     <el-button icon="Document" :disabled="applicationIds.length !== 1" @click="handleExportResume">导出简历</el-button>

+ 6 - 6
web/src/views/rs/overseas/approval.vue

@@ -83,9 +83,9 @@
               </template>
 
               <!-- 待审批申请表格 -->
-              <el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleViewApplication">
-                <el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
-                <el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
+                          <el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleViewApplication" v-hasPermi="['rs:overseas:list']">
+              <el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
+              <el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
                 <el-table-column label="出国目的" align="center" prop="purposeText" width="100" :show-overflow-tooltip="true" />
                 <el-table-column label="目的地" align="center" prop="destination" width="120" :show-overflow-tooltip="true" />
                 <el-table-column label="出国时间" align="center" min-width="220" :show-overflow-tooltip="true">
@@ -226,9 +226,9 @@
               </template>
 
               <!-- 审批记录表格 -->
-              <el-table v-loading="recordLoading" border :data="recordList" @row-dblclick="handleViewRecord">
-                <el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
-                <el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
+                          <el-table v-loading="recordLoading" border :data="recordList" @row-dblclick="handleViewRecord" v-hasPermi="['rs:overseas:list']">
+              <el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
+              <el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
                 <el-table-column label="出国目的" align="center" prop="purposeText" width="100" :show-overflow-tooltip="true" />
                 <el-table-column label="目的地" align="center" prop="destination" width="120" :show-overflow-tooltip="true" />
                 <el-table-column label="出国时间" align="center" min-width="220" :show-overflow-tooltip="true">

+ 37 - 1
web/src/views/rs/overseas/index.vue

@@ -328,7 +328,12 @@
 
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" :loading="state.submitLoading" @click="submitApplicationForm">确 定</el-button>
+          <el-button 
+            type="primary" 
+            :loading="state.submitLoading" 
+            @click="submitApplicationForm"
+            v-hasPermi="state.applicationForm.applicationId ? ['rs:overseas:edit'] : ['rs:overseas:add', 'rs:overseas:submit']"
+          >确 定</el-button>
           <el-button @click="cancelApplicationForm">取 消</el-button>
         </div>
       </template>
@@ -470,6 +475,7 @@ import {
 import { ElMessage, ElMessageBox } from 'element-plus';
 import animate from '@/animate';
 import { parseTime } from '@/utils/ruoyi';
+import { checkPermi } from '@/utils/permission';
 
 // 响应式数据
 const { proxy } = getCurrentInstance() as any;
@@ -759,6 +765,12 @@ const handleViewApplication = async (row: OverseasApplicationVO) => {
 
 // 提交申请
 const handleSubmitApplication = (row: OverseasApplicationVO) => {
+  // 权限检查
+  if (!checkPermi(['rs:overseas:submit'])) {
+    ElMessage.error('您没有提交权限');
+    return;
+  }
+
   ElMessageBox.confirm(`确认重新提交申请"${row.applicationId}"?`, '提示', {
     confirmButtonText: '确定',
     cancelButtonText: '取消',
@@ -965,6 +977,30 @@ const resetApplicationForm = () => {
 const submitApplicationForm = () => {
   applicationFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
+      // 权限检查
+      if (state.applicationForm.applicationId) {
+        // 编辑模式:检查编辑权限
+        if (!checkPermi(['rs:overseas:edit'])) {
+          ElMessage.error('您没有编辑权限');
+          return;
+        }
+        // 重新提交模式:额外检查提交权限
+        if (state.isResubmitMode && !checkPermi(['rs:overseas:submit'])) {
+          ElMessage.error('您没有提交权限');
+          return;
+        }
+      } else {
+        // 新增模式:检查新增和提交权限
+        if (!checkPermi(['rs:overseas:add'])) {
+          ElMessage.error('您没有新增权限');
+          return;
+        }
+        if (!checkPermi(['rs:overseas:submit'])) {
+          ElMessage.error('您没有提交权限');
+          return;
+        }
+      }
+
       state.submitLoading = true;
       try {
         if (state.applicationForm.applicationId) {

+ 2 - 0
web/src/views/rs/overseasApproval/index.vue

@@ -80,6 +80,7 @@
                 style="width: 100%"
                 class="overseas-approval-table"
                 empty-text="暂无数据"
+                v-hasPermi="['rs:overseas:list']"
               >
                 <el-table-column type="selection" width="55" align="center" />
                 <el-table-column label="申请人" align="center" prop="applicantName" width="100" show-overflow-tooltip />
@@ -202,6 +203,7 @@
                 @row-dblclick="handleViewRecord"
                 style="width: 100%"
                 empty-text="暂无数据"
+                v-hasPermi="['rs:overseas:list']"
               >
                 <el-table-column type="selection" width="55" align="center" />
                 <el-table-column label="申请人" align="center" prop="applicantName" width="100" show-overflow-tooltip />

+ 131 - 128
web/src/views/rs/training/index.vue

@@ -3,7 +3,7 @@
     <el-card class="box-card">
       <!-- 培训申请子选项卡 -->
       <el-tabs v-model="trainingSubTab" @tab-click="handleTrainingSubTabClick">
-        <el-tab-pane label="培训申请" name="application">
+        <el-tab-pane label="培训申请" name="application" v-hasPermi="['rs:trainingapplication:list']">
           <!-- 搜索区域 -->
           <transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
             <div v-show="showApplicationSearch" class="mb-[10px]">
@@ -148,7 +148,7 @@
           </el-card>
         </el-tab-pane>
 
-        <el-tab-pane label="培训记录" name="record">
+        <el-tab-pane label="培训记录" name="record" v-hasPermi="['rs:trainingrecord:list']">
           <!-- 搜索区域 -->
           <transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
             <div v-show="showRecordSearch" class="mb-[10px]">
@@ -190,7 +190,7 @@
             <template #header>
               <el-row :gutter="10" class="mb8">
                 <el-col :span="1.5">
-                  <el-button icon="Download" @click="handleExportRecord" v-hasPermi="['rs:training:record:export']" style="display: none">导出</el-button>
+                  <el-button icon="Download" @click="handleExportRecord" v-hasPermi="['rs:trainingrecord:export']" style="display: none">导出</el-button>
                 </el-col>
                 <right-toolbar v-model:showSearch="showRecordSearch" @queryTable="getRecordList"></right-toolbar>
               </el-row>
@@ -227,7 +227,7 @@
               <el-table-column label="操作" align="center" width="200">
                 <template #default="scope">
                   <el-tooltip content="查看" placement="top">
-                    <el-button link type="primary" icon="View" @click.stop="handleViewRecord(scope.row)"></el-button>
+                    <el-button link type="primary" icon="View" @click.stop="handleViewRecord(scope.row)" v-hasPermi="['rs:trainingrecord:query']"></el-button>
                   </el-tooltip>
                   <el-tooltip content="编辑" placement="top">
                     <el-button
@@ -235,7 +235,7 @@
                       type="primary"
                       icon="Edit"
                       @click.stop="handleUpdateRecord(scope.row)"
-                      v-hasPermi="['rs:training:record:edit']"
+                      v-hasPermi="['rs:trainingrecord:edit']"
                     ></el-button>
                   </el-tooltip>
                   <el-tooltip content="删除" placement="top">
@@ -244,7 +244,7 @@
                       type="danger"
                       icon="Delete"
                       @click.stop="handleDeleteRecord(scope.row)"
-                      v-hasPermi="['rs:training:record:remove']"
+                      v-hasPermi="['rs:trainingrecord:remove']"
                     ></el-button>
                   </el-tooltip>
                 </template>
@@ -448,6 +448,14 @@
         <el-form-item label="学时" prop="duration">
           <el-input-number v-model="applicationForm.duration" :min="0" :max="1000" :step="0.5" style="width: 200px" />
         </el-form-item>
+        <el-form-item label="申请人" prop="userName">
+          <el-input 
+            v-model="applicationForm.userName" 
+            placeholder="申请人姓名" 
+            readonly 
+            class="readonly-input"
+          />
+        </el-form-item>
         <el-form-item label="所属部门" prop="department">
           <el-input 
             v-model="applicationForm.department" 
@@ -744,13 +752,8 @@ import {
 } from '@element-plus/icons-vue';
 import ImagePreview from '@/components/ImagePreview/index.vue';
 import {
-  listTrainingCourse,
-  getTrainingCourse,
-  addTrainingCourse,
-  updateTrainingCourse,
-  delTrainingCourse,
-  toggleCourseStatus,
-  exportTrainingCourse,
+  // 培训课程相关接口已废弃 (2025-01-03)
+  // listTrainingCourse, getTrainingCourse, addTrainingCourse, updateTrainingCourse, delTrainingCourse, toggleCourseStatus, exportTrainingCourse,
   listTrainingApplication,
   getTrainingApplication,
   addTrainingApplication,
@@ -945,6 +948,7 @@ const applicationQueryParams = reactive({
   location: '',
   duration: undefined as number | undefined,
   status: '',
+  excludeStatus: '1', // 默认排除已通过状态的数据
   params: {}
 });
 
@@ -992,6 +996,7 @@ const showRecordSearch = ref(true);
   userName: '',
   department: '',
   completionStatus: '',
+  includeApprovedApplications: true, // 包含已通过审批的申请数据
   params: {}
 });
 
@@ -1073,6 +1078,7 @@ const applicationFormRef = ref<any>(null);
 const applicationForm = reactive<{
   applicationId?: number | string;
   courseName: string;
+  userName: string; // 申请人姓名
   department: string;
   location: string;
   duration: number;
@@ -1083,6 +1089,7 @@ const applicationForm = reactive<{
 }>({
   applicationId: undefined,
   courseName: '',
+  userName: '', // 申请人姓名
   department: '',
   location: '',
   duration: 0,
@@ -1106,14 +1113,14 @@ const applicationRules = reactive({
 // 部门选项 - 从数据库sys_dept表获取
 const departmentOptions = ref<Array<{ value: string; label: string }>>([]);
 
-/** 查询培训课程列表 */
+/** 查询培训课程列表 - 已废弃 (2025-01-03) */
 function getTrainingList() {
   trainingLoading.value = true;
-  listTrainingCourse(trainingQueryParams).then((response) => {
-    courseList.value = response.rows;
-    trainingTotal.value = response.total;
-    trainingLoading.value = false;
-  });
+  // 培训课程模块已废弃,课程信息现在通过培训申请管理
+  console.warn('培训课程模块已废弃,请使用培训申请功能');
+  courseList.value = [];
+  trainingTotal.value = 0;
+  trainingLoading.value = false;
 }
 
 /** 获取部门列表 */
@@ -1199,11 +1206,9 @@ function handleUpdateCourse(row?: CourseVO) {
     return;
   }
 
-  getTrainingCourse(courseId).then((response) => {
-    Object.assign(courseForm, response.data);
-    courseDialogVisible.value = true;
-    courseDialogTitle.value = '修改培训课程';
-  });
+  // 培训课程模块已废弃 (2025-01-03)
+  console.warn('培训课程编辑功能已废弃,请使用培训申请功能');
+  proxy?.$modal.msgWarning('培训课程模块已废弃,请使用培训申请功能');
 }
 
 /** 提交按钮 */
@@ -1211,27 +1216,10 @@ function submitCourseForm() {
   courseFormRef.value?.validate((valid: boolean) => {
     if (valid) {
       courseDialogLoading.value = true;
-      if (courseForm.courseId) {
-        updateTrainingCourse(courseForm)
-          .then(() => {
-            proxy?.$modal.msgSuccess('修改成功');
-            courseDialogVisible.value = false;
-            getTrainingList();
-          })
-          .finally(() => {
-            courseDialogLoading.value = false;
-          });
-      } else {
-        addTrainingCourse(courseForm)
-          .then(() => {
-            proxy?.$modal.msgSuccess('新增成功');
-            courseDialogVisible.value = false;
-            getTrainingList();
-          })
-          .finally(() => {
-            courseDialogLoading.value = false;
-          });
-      }
+      // 培训课程模块已废弃 (2025-01-03)
+      console.warn('培训课程新增/编辑功能已废弃,请使用培训申请功能');
+      proxy?.$modal.msgWarning('培训课程模块已废弃,请使用培训申请功能');
+      courseDialogLoading.value = false;
     }
   });
 }
@@ -1259,11 +1247,9 @@ function handleDeleteCourse(row?: CourseVO) {
   proxy?.$modal
     .confirm(confirmMessage)
     .then(() => {
-      return delTrainingCourse(courseIds);
-    })
-    .then(() => {
-      getTrainingList();
-      proxy?.$modal.msgSuccess('删除成功');
+      // 培训课程模块已废弃 (2025-01-03)
+      console.warn('培训课程删除功能已废弃,请使用培训申请功能');
+      proxy?.$modal.msgWarning('培训课程模块已废弃,请使用培训申请功能');
     });
 }
 
@@ -1278,26 +1264,16 @@ function handleExportCourse() {
   );
 }
 
-/** 查看培训课程详情 */
+/** 查看培训课程详情 - 已废弃 (2025-01-03) */
 function handleViewCourse(row: CourseVO) {
-  getTrainingCourse(row.courseId).then((response) => {
-    Object.assign(courseDetail, response.data);
-    courseDetailDialogVisible.value = true;
-  });
+  console.warn('培训课程查看功能已废弃,请使用培训申请功能');
+  proxy?.$modal.msgWarning('培训课程模块已废弃,请使用培训申请功能');
 }
 
-/** 切换课程状态 */
+/** 切换课程状态 - 已废弃 (2025-01-03) */
 function handleToggleStatus(row: CourseVO) {
-  const text = row.status === '0' ? '结束' : '启用';
-  proxy?.$modal
-    .confirm(`确认要${text}该课程吗?`)
-    .then(() => {
-      return toggleCourseStatus(row.courseId);
-    })
-    .then(() => {
-      getTrainingList();
-      proxy?.$modal.msgSuccess(`${text}成功`);
-    });
+  console.warn('培训课程状态切换功能已废弃,请使用培训申请功能');
+  proxy?.$modal.msgWarning('培训课程模块已废弃,请使用培训申请功能');
 }
 
 /** 查看培训申请记录 */
@@ -1313,13 +1289,25 @@ function handleViewApplications(row: CourseVO) {
 /** 培训申请管理相关 */
 function getApplicationList() {
   applicationLoading.value = true;
+  
+  // 构建查询参数,自动过滤掉已通过的数据
+  const queryParams = {
+    ...applicationQueryParams,
+    // 排除已通过状态的数据,只显示待审核、已拒绝、已取消等状态
+    excludeStatus: '1' // 1表示已通过状态
+  };
+  
   // 调用API获取申请列表
-  listTrainingApplication(applicationQueryParams)
+  listTrainingApplication(queryParams)
     .then((response) => {
       // 获取申请列表数据
       const applications = response.rows || [];
-      applicationList.value = applications;
-      applicationTotal.value = response.total || 0;
+      
+      // 前端二次过滤,确保不显示已通过的数据
+      const filteredApplications = applications.filter(app => app.status !== '1');
+      
+      applicationList.value = filteredApplications;
+      applicationTotal.value = filteredApplications.length;
       applicationLoading.value = false;
     })
     .catch(() => {
@@ -1339,68 +1327,82 @@ async function handleAddApplication() {
     loadCourseOptions();
   }
 
-  // 自动获取登录用户的部门信息
-  try {
-    // 确保用户信息已加载
-    if (!userStore.userId) {
-      await userStore.getInfo();
-    }
-    
-    console.log('开始自动填充用户部门信息:', {
-      userId: userStore.userId,
-      cachedDeptName: userStore.deptName
-    });
+      // 自动获取登录用户的部门信息和申请人信息
+    try {
+      // 确保用户信息已加载
+      if (!userStore.userId) {
+        await userStore.getInfo();
+      }
+      
+      console.log('开始自动填充用户信息:', {
+        userId: userStore.userId,
+        userName: userStore.name,
+        nickname: userStore.nickname,
+        cachedDeptName: userStore.deptName
+      });
 
-    // 重新获取用户信息以确保获取到deptId
-    await userStore.getInfo();
-    const userInfo = await getUserInfo();
-    const userDeptId = userInfo.data.user.deptId;
-    
-    console.log('用户信息详情:', {
-      userId: userStore.userId,
-      deptId: userDeptId,
-      cachedDeptName: userStore.deptName
-    });
+      // 自动填充申请人姓名(使用用户名称)
+      const displayName = userStore.name || `用户${userStore.userId}`;
+      applicationForm.userName = displayName;
+      
+      console.log('申请人姓名自动填充:', {
+        userId: userStore.userId,
+        displayName: displayName,
+        userName: userStore.name
+      });
 
-    // 如果有deptId,从数据库sys_dept表实时获取部门名称
-    if (userDeptId) {
-      console.log('开始从数据库sys_dept表获取部门信息 - deptId:', userDeptId);
+      // 重新获取用户信息以确保获取到deptId
+      await userStore.getInfo();
+      const userInfo = await getUserInfo();
+      const userDeptId = userInfo.data.user.deptId;
       
-      try {
-        const deptResponse = await getDept(userDeptId);
-        if (deptResponse.code === 200 && deptResponse.data) {
-          const realDeptName = deptResponse.data.deptName;
-          applicationForm.department = realDeptName;
-          
-          console.log('培训申请部门自动匹配成功:', {
-            userId: userStore.userId,
-            userDeptId: userDeptId,
-            deptNameFromDatabase: realDeptName,
-            关联逻辑: 'sys_user.dept_id -> sys_dept.dept_name'
-          });
-        } else {
-          console.warn('从数据库获取部门信息失败,使用缓存的部门名称');
+      console.log('用户信息详情:', {
+        userId: userStore.userId,
+        deptId: userDeptId,
+        cachedDeptName: userStore.deptName
+      });
+
+      // 如果有deptId,从数据库sys_dept表实时获取部门名称
+      if (userDeptId) {
+        console.log('开始从数据库sys_dept表获取部门信息 - deptId:', userDeptId);
+        
+        try {
+          const deptResponse = await getDept(userDeptId);
+          if (deptResponse.code === 200 && deptResponse.data) {
+            const realDeptName = deptResponse.data.deptName;
+            applicationForm.department = realDeptName;
+            
+            console.log('培训申请部门自动匹配成功:', {
+              userId: userStore.userId,
+              userDeptId: userDeptId,
+              deptNameFromDatabase: realDeptName,
+              关联逻辑: 'sys_user.dept_id -> sys_dept.dept_name'
+            });
+          } else {
+            console.warn('从数据库获取部门信息失败,使用缓存的部门名称');
+            applicationForm.department = userStore.deptName || '';
+          }
+        } catch (deptError) {
+          console.error('查询部门信息API调用失败:', deptError);
           applicationForm.department = userStore.deptName || '';
         }
-      } catch (deptError) {
-        console.error('查询部门信息API调用失败:', deptError);
+      } else {
+        console.warn('用户deptId不存在,使用缓存的部门名称');
         applicationForm.department = userStore.deptName || '';
       }
-    } else {
-      console.warn('用户deptId不存在,使用缓存的部门名称');
-      applicationForm.department = userStore.deptName || '';
+    } catch (error) {
+      console.error('获取用户信息失败:', error);
+      // 如果获取失败,使用空字符串,用户可以手动输入
+      applicationForm.department = '';
+      applicationForm.userName = `用户${userStore.userId || '未知'}`;
     }
-  } catch (error) {
-    console.error('获取用户信息失败:', error);
-    // 如果获取失败,使用空字符串,用户可以手动输入
-    applicationForm.department = '';
-  }
 }
 
 /** 重置培训申请表单 */
 function resetApplicationForm() {
   applicationForm.applicationId = undefined;
   applicationForm.courseName = '';
+  applicationForm.userName = ''; // 重置申请人字段
   applicationForm.department = '';
   applicationForm.location = '';
   applicationForm.duration = 0;
@@ -1778,16 +1780,12 @@ function handleTrainingSubTabClick() {
   }
 }
 
-// 加载课程选项
+// 加载课程选项 - 已废弃 (2025-01-03)
 function loadCourseOptions() {
-  return listTrainingCourse({ pageNum: 1, pageSize: 100, status: '0' }).then((response) => {
-    courseOptions.value = response.rows || [];
-    return courseOptions.value;
-  }).catch((error) => {
-    console.error('加载课程选项失败:', error);
-    courseOptions.value = [];
-    throw error;
-  });
+  // 培训课程模块已废弃,现在课程信息通过申请时的自定义课程名称管理
+  console.warn('培训课程选项已废弃,现在使用自定义课程名称');
+  courseOptions.value = [];
+  return Promise.resolve([]);
 }
 
 // 远程搜索用户
@@ -1818,10 +1816,13 @@ function remoteSearchUser(query: string) {
 /** 获取培训记录列表 */
 function getRecordList() {
   recordLoading.value = true;
-  // 设置当前用户ID,只查询当前用户的培训记录
+  
+  // 构建查询参数,包含已通过审批的申请数据
   const queryParams = {
     ...recordQueryParams,
-    userId: userStore.userId // 设置当前用户ID
+    userId: userStore.userId, // 设置当前用户ID
+    // 确保包含已通过状态的培训申请数据
+    includeApprovedApplications: true
   };
   
   // 调用培训记录接口,获取培训记录数据
@@ -2973,6 +2974,8 @@ function resetApplicationQuery() {
   applicationQueryParams.location = '';
   applicationQueryParams.duration = undefined;
   applicationQueryParams.status = '';
+  // 保持排除已通过状态的设置
+  applicationQueryParams.excludeStatus = '1';
   handleApplicationQuery();
 }
 

+ 23 - 6
web/src/views/rs/trainingApproval/index.vue

@@ -175,7 +175,7 @@
             <template #header>
               <el-row :gutter="10" class="mb8">
                 <el-col :span="1.5">
-                  <el-button icon="Download" @click="handleExportApprovalRecord" v-hasPermi="['rs:trainingapproval:export']">导出</el-button>
+                  <el-button icon="Download" @click="handleExportApprovalRecord" v-hasPermi="['rs:trainingapproval:export']" style="display: none">导出</el-button>
                 </el-col>
                 <right-toolbar v-model:showSearch="showApprovalRecordSearch" @queryTable="getApprovalRecordList"></right-toolbar>
               </el-row>
@@ -551,6 +551,7 @@ const applicationQueryParams = reactive({
   courseName: '',
   location: '',
   status: '',
+  excludeStatus: '1', // 默认排除已通过状态的数据
   params: {}
 });
 
@@ -600,6 +601,7 @@ const approvalRecordQueryParams = reactive({
   courseName: '',
   userName: '',
   status: '',
+  includeApprovedApplications: true, // 包含已通过审批的申请数据
   params: {}
 });
 
@@ -665,9 +667,20 @@ const getStatusLabel = (status: string) => {
 const getApplicationList = async () => {
   applicationLoading.value = true;
   try {
-    const response = await listTrainingApplication(applicationQueryParams);
-    applicationList.value = response.rows;
-    applicationTotal.value = response.total;
+    // 构建查询参数,自动过滤掉已通过的数据
+    const queryParams = {
+      ...applicationQueryParams,
+      // 排除已通过状态的数据,只显示待审核、已拒绝、已取消等状态
+      excludeStatus: '1' // 1表示已通过状态
+    };
+    
+    const response = await listTrainingApplication(queryParams);
+    
+    // 前端二次过滤,确保不显示已通过的数据
+    const filteredApplications = response.rows.filter(app => app.status !== '1');
+    
+    applicationList.value = filteredApplications;
+    applicationTotal.value = filteredApplications.length;
   } catch (error) {
     console.error('获取申请列表失败:', error);
   } finally {
@@ -689,6 +702,7 @@ const resetApplicationQuery = () => {
     courseName: '',
     location: '',
     status: '',
+    excludeStatus: '1', // 保持排除已通过状态的设置
     params: {}
   });
   getApplicationList();
@@ -780,7 +794,8 @@ const getApprovalRecordList = async () => {
     // 获取所有审批记录(不限于当前用户审批过的)
     const queryParams = {
       ...approvalRecordQueryParams,
-      status: approvalRecordQueryParams.status || '1,2' // 默认获取已通过和已拒绝的记录
+      status: approvalRecordQueryParams.status || '1,2', // 默认获取已通过和已拒绝的记录
+      includeApprovedApplications: true // 确保包含已通过状态的培训申请数据
     };
     const response = await listAllApprovalRecords(queryParams);
     approvalRecordList.value = response.rows;
@@ -809,6 +824,7 @@ const resetApprovalRecordQuery = () => {
     courseName: '',
     userName: '',
     status: '',
+    includeApprovedApplications: true, // 保持包含已通过审批的申请数据
     params: {}
   });
   getApprovalRecordList();
@@ -858,7 +874,8 @@ const handleExportApprovalRecord = async () => {
   try {
     const queryParams = {
       ...approvalRecordQueryParams,
-      status: approvalRecordQueryParams.status || '1,2' // 默认导出已通过和已拒绝的记录
+      status: approvalRecordQueryParams.status || '1,2', // 默认导出已通过和已拒绝的记录
+      includeApprovedApplications: true // 确保包含已通过状态的培训申请数据
     };
     const response = await exportApprovalRecord(queryParams);