From 53e5afaafe4117d7e8cce02c75605bbff3641303 Mon Sep 17 00:00:00 2001 From: weizhong Date: Thu, 6 Nov 2025 13:53:52 +0800 Subject: [PATCH] =?UTF-8?q?=E2=80=9C=E5=9F=B9=E8=AE=AD=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E4=BF=AE=E6=94=B9v1.2=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RsTrainingApplicationController.java | 78 ++++++++++- web/src/api/rs/training.ts | 26 ++++ web/src/views/rs/training/index.vue | 122 +++++++++++------- 3 files changed, 180 insertions(+), 46 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java index 361aa041..20f43e90 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java @@ -19,7 +19,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.dev33.satoken.annotation.SaIgnore; import cn.dev33.satoken.stp.StpUtil; import jakarta.servlet.http.HttpServletResponse; @@ -28,6 +27,10 @@ import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.service.ISysUserService; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.rs.domain.vo.SysUserBasicVo; +import cn.hutool.core.collection.CollUtil; + +import java.util.ArrayList; /** * 培训申请控制器 @@ -439,4 +442,77 @@ public class RsTrainingApplicationController extends BaseController { return R.fail("查询失败: " + e.getMessage()); } } + + /** + * 批量查询同行人基本信息 + * 用于培训申请/记录详情展示,不限制account_type + * + * 权限说明: + * - 只需要培训申请的查询权限即可 + * - 不需要system:teacher:list权限 + * - 只返回用户的基本信息(姓名、部门等),不暴露敏感数据 + * + * @param userIds 用户ID列表(逗号分隔) + * @return 用户基本信息列表 + */ + @SaCheckPermission("rs:trainingapplication:query") + @GetMapping("/companions/batch") + public R> batchQueryCompanions(@RequestParam String userIds) { + log.info("批量查询同行人信息,用户ID列表: {}", userIds); + + // 参数验证 + if (StringUtils.isBlank(userIds)) { + log.warn("同行人ID列表为空"); + return R.ok(new ArrayList<>()); + } + + // 解析用户ID列表 + List userIdList = new ArrayList<>(); + try { + String[] idArray = userIds.split(","); + for (String idStr : idArray) { + if (StringUtils.isNotBlank(idStr)) { + userIdList.add(Long.parseLong(idStr.trim())); + } + } + } catch (NumberFormatException e) { + log.error("解析用户ID失败: {}", e.getMessage()); + return R.fail("用户ID格式错误"); + } + + if (CollUtil.isEmpty(userIdList)) { + log.warn("解析后的用户ID列表为空"); + return R.ok(new ArrayList<>()); + } + + // 批量查询用户信息 + List userList = new ArrayList<>(); + for (Long userId : userIdList) { + try { + // 调用用户服务查询 + SysUserVo sysUser = sysUserService.selectUserById(userId); + if (sysUser != null) { + SysUserBasicVo vo = new SysUserBasicVo(); + vo.setUserId(sysUser.getUserId()); + vo.setUserName(sysUser.getUserName()); + vo.setNickName(sysUser.getNickName()); + vo.setAccountType(sysUser.getAccountType()); + vo.setStatus(sysUser.getStatus()); + vo.setDeptId(sysUser.getDeptId()); + // 设置部门名称(SysUserVo已经包含deptName字段) + vo.setDeptName(sysUser.getDeptName()); + userList.add(vo); + log.debug("查询到用户: userId={}, nickName={}", userId, sysUser.getNickName()); + } else { + log.warn("用户不存在: userId={}", userId); + } + } catch (Exception e) { + log.error("查询用户{}失败: {}", userId, e.getMessage()); + // 继续查询下一个用户,不中断整个流程 + } + } + + log.info("批量查询同行人完成,请求{}个,查询到{}个", userIdList.size(), userList.size()); + return R.ok(userList); + } } \ No newline at end of file diff --git a/web/src/api/rs/training.ts b/web/src/api/rs/training.ts index deb292f3..b0ad8928 100644 --- a/web/src/api/rs/training.ts +++ b/web/src/api/rs/training.ts @@ -458,3 +458,29 @@ export function getPendingTrainingApplications() { method: 'get' }); } + +/** + * 批量查询同行人基本信息 + * 用于培训申请/记录详情展示 + * @param userIds 用户ID数组 + * @returns 用户基本信息列表 + */ +export interface UserBasicVO { + userId: number; + userName: string; + nickName: string; + accountType: string; + status: string; + deptId?: number; + deptName?: string; +} + +export function batchQueryCompanions(userIds: number[]) { + return request({ + url: '/rs/training/application/companions/batch', + method: 'get', + params: { + userIds: userIds.join(',') + } + }); +} diff --git a/web/src/views/rs/training/index.vue b/web/src/views/rs/training/index.vue index 291ad56b..fd4aebb2 100644 --- a/web/src/views/rs/training/index.vue +++ b/web/src/views/rs/training/index.vue @@ -999,6 +999,7 @@ import { deleteTrainingApplication, getTrainingApprovalHistory, resubmitTrainingApplication, + batchQueryCompanions, updateRecordAttachments, listAllApprovalRecords } from '@/api/rs/training'; @@ -1432,6 +1433,9 @@ const applicationForm = reactive<{ // 同行人选择器相关 const selectedCompanions = ref([]); +// 同行人姓名缓存(用于详情展示) +const companionNamesCache = ref>({}); + // 培训申请表单校验规则 const applicationRules = reactive({ // 情况基本信息验证 @@ -1716,7 +1720,7 @@ function getTeacherList() { }); } -/** 异步加载教师列表用于详情显示 */ +/** 异步加载教师列表用于详情显示(容错版,权限不足时不报错)*/ async function loadTeacherListForDisplay() { try { console.log('开始加载教师列表...'); @@ -1733,8 +1737,9 @@ async function loadTeacherListForDisplay() { })); console.log('教师列表加载完成:', teacherList.value.length, '位教师'); console.log('教师ID列表:', teacherList.value.map(t => t.teacherId)); - } catch (error) { - console.error('获取教师列表失败:', error); + } catch (error: any) { + console.warn('获取教师列表失败(可能是权限不足),将使用备用查询方式:', error); + // ⚠️ 不再抛出错误,允许使用备用的同行人查询接口 teacherList.value = []; } } @@ -2131,66 +2136,91 @@ function formatFileSize(bytes: number | string | undefined): string { return parseFloat((size / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } -/** 获取同行人姓名列表 */ -function getCompanionNames(companions: (string | number)[] | string | null | undefined): string { +/** 异步加载同行人姓名并缓存(使用新的批量查询接口)*/ +async function loadCompanionNames(companions: (string | number)[] | string | null | undefined): Promise { if (!companions) { - console.log('[同行人解析] 同行人数据为空'); return ''; } - let companionArray: (string | number)[] = []; + // 生成缓存key + const cacheKey = typeof companions === 'string' ? companions : JSON.stringify(companions); - // 如果是字符串,尝试解析JSON + // 如果已缓存,直接返回 + if (companionNamesCache.value[cacheKey]) { + return companionNamesCache.value[cacheKey]; + } + + let companionArray: number[] = []; + + // 解析同行人ID列表 if (typeof companions === 'string') { try { - companionArray = JSON.parse(companions); - if (!Array.isArray(companionArray)) { - console.log('[同行人解析] JSON解析结果不是数组:', companionArray); - return ''; + const parsed = JSON.parse(companions); + if (Array.isArray(parsed)) { + companionArray = parsed.map(id => Number(id)); } console.log('[同行人解析] JSON解析成功:', companionArray); } catch (e) { // 如果JSON解析失败,可能是逗号分隔的字符串 - companionArray = companions.split(',').map(id => id.trim()).filter(id => id); + companionArray = companions.split(',') + .map(id => Number(id.trim())) + .filter(id => !isNaN(id)); console.log('[同行人解析] 使用逗号分隔解析:', companionArray); } } else if (Array.isArray(companions)) { - companionArray = companions; + companionArray = companions.map(id => Number(id)); console.log('[同行人解析] 直接使用数组:', companionArray); } if (companionArray.length === 0) { - console.log('[同行人解析] 同行人数组为空'); return ''; } - // 如果教师列表还未加载,显示"加载中..." - if (teacherList.value.length === 0) { - console.log('[同行人解析] 教师列表未加载,返回加载中'); - return '加载中...'; + try { + // 调用新的批量查询接口(不需要system:teacher:list权限) + console.log('[同行人查询] 调用批量查询接口:', companionArray); + const response: any = await batchQueryCompanions(companionArray); + const users = response.data || []; + + console.log('[同行人查询] 查询结果:', users.length, '个用户'); + + // 拼接姓名 + const names = companionArray.map(userId => { + const user = users.find((u: any) => u.userId === userId); + const name = user ? (user.nickName || user.userName) : `未知用户(ID:${userId})`; + console.log(`[同行人查询] ID ${userId} -> ${name}`); + return name; + }).join('、'); + + // 缓存结果 + companionNamesCache.value[cacheKey] = names; + console.log('[同行人查询] 最终结果:', names); + return names; + } catch (error) { + console.error('[同行人查询] 接口调用失败:', error); + // 降级方案:显示ID列表 + const fallback = `同行人ID: ${companionArray.join('、')}`; + companionNamesCache.value[cacheKey] = fallback; + return fallback; } - - console.log('[同行人解析] 教师列表已加载:', teacherList.value.length, '位教师'); - console.log('[同行人解析] 需要查找的ID:', companionArray); - - const names = companionArray.map(teacherId => { - // 确保teacherId转换为正确的类型进行比较 - const teacher = teacherList.value.find(t => - String(t.teacherId) === String(teacherId) || - Number(t.teacherId) === Number(teacherId) - ); - const name = teacher ? teacher.teacherName : `未知教师(ID:${teacherId})`; - console.log(`[同行人解析] ID ${teacherId} -> ${name}`); - return name; - }).join('、'); - - console.log('[同行人解析] 最终结果:', names); - return names; } -/** 格式化同行人员显示(表格列使用) */ -function formatCompanions(companions: string | undefined): string { - return getCompanionNames(companions); +/** 获取同行人姓名列表(同步版,从缓存读取)*/ +function getCompanionNames(companions: (string | number)[] | string | null | undefined): string { + if (!companions) { + return ''; + } + + const cacheKey = typeof companions === 'string' ? companions : JSON.stringify(companions); + + // 从缓存返回 + if (companionNamesCache.value[cacheKey]) { + return companionNamesCache.value[cacheKey]; + } + + // 如果缓存中没有,返回"加载中..."并异步加载 + loadCompanionNames(companions); + return '加载中...'; } /** 查看申请详情 */ @@ -2210,20 +2240,22 @@ async function handleViewApplicant(row: any) { } } - // 确保教师列表已加载(用于显示同行人姓名) - if (teacherList.value.length === 0) { - await loadTeacherListForDisplay(); - } - // 复制申请详情 Object.assign(applicationDetail, row); console.log('[查看申请详情] 详情数据已设置:', { userName: applicationDetail.userName, nickName: applicationDetail.nickName, - attachmentIds: applicationDetail.attachmentIds // 添加附件ID日志 + companions: applicationDetail.companions, + attachmentIds: applicationDetail.attachmentIds }); + // 预加载同行人信息(使用新接口,不需要system:teacher:list权限) + if (row.companions) { + await loadCompanionNames(row.companions); + console.log('[查看申请详情] 同行人信息已预加载'); + } + // 加载申请详情的附件数据 await handleApplicationDetailAttachmentData(row.attachmentIds);