Procházet zdrojové kódy

“rs招聘和培训修改”

weizhong před 1 rokem
rodič
revize
e6201af3db

+ 52 - 0
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingApplication.java

@@ -9,6 +9,7 @@ import org.dromara.common.mybatis.core.domain.BaseEntity;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.baomidou.mybatisplus.annotation.IdType;
 
+import java.math.BigDecimal;
 import java.util.Date;
 
 /**
@@ -60,6 +61,52 @@ public class RsTrainingApplication extends BaseEntity {
      */
     private String position;
 
+    /**
+     * 现职称
+     */
+    private String currentTitle;
+
+    /**
+     * 现岗位
+     */
+    private String currentPosition;
+
+    /**
+     * 参加工作时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date workStartTime;
+
+    /**
+     * 联系电话
+     */
+    private String phone;
+
+    /**
+     * 主办单位
+     */
+    private String organizerUnit;
+
+    /**
+     * 培训类型
+     */
+    private String trainingType;
+
+    /**
+     * 培训费用
+     */
+    private BigDecimal trainingFee;
+
+    /**
+     * 同行人员(JSON格式)
+     */
+    private String companions;
+
+    /**
+     * 培训目的
+     */
+    private String trainingPurpose;
+
     /**
      * 申请理由
      */
@@ -118,6 +165,11 @@ public class RsTrainingApplication extends BaseEntity {
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date rejectTime;
+
+    /**
+     * 附件ID列表(逗号分隔)
+     */
+    private String attachmentIds;
     
     /**
      * 排除状态(查询时排除指定状态,不存储到数据库)

+ 60 - 1
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingApproval.java

@@ -70,10 +70,69 @@ public class RsTrainingApproval extends BaseEntity {
      */
     private String tenantId;
     
-    /** 非数据库字段 **/
+    /** 非数据库字段 - 审批记录列表显示 **/
     @TableField(exist = false)
     private String applicationUserName;
     
     @TableField(exist = false)
     private String courseTitle;
+    
+    /** 非数据库字段 - 审批记录详情显示 **/
+    @TableField(exist = false)
+    private String userName;
+    
+    @TableField(exist = false)
+    private String department;
+    
+    @TableField(exist = false)
+    private String currentTitle;
+    
+    @TableField(exist = false)
+    private String currentPosition;
+    
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date workStartTime;
+    
+    @TableField(exist = false)
+    private String phone;
+    
+    @TableField(exist = false)
+    private String organizerUnit;
+    
+    @TableField(exist = false)
+    private String courseName;
+    
+    @TableField(exist = false)
+    private String location;
+    
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+    
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+    
+    @TableField(exist = false)
+    private String trainingType;
+    
+    @TableField(exist = false)
+    private Double trainingFee;
+    
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date applyTime;
+    
+    @TableField(exist = false)
+    private String companions;
+    
+    @TableField(exist = false)
+    private String trainingPurpose;
+    
+    @TableField(exist = false)
+    private String reason;
+    
+    @TableField(exist = false)
+    private String attachmentIds;
 } 

+ 8 - 0
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsTrainingApprovalMapper.java

@@ -20,4 +20,12 @@ public interface RsTrainingApprovalMapper extends BaseMapperPlus<RsTrainingAppro
      */
     List<RsTrainingApproval> selectRsTrainingApprovalWithDetailsList(RsTrainingApproval approval);
 
+    /**
+     * 查询带详细信息的审批记录数量(关联申请人和课程信息)
+     * 
+     * @param approval 查询条件
+     * @return 记录数量
+     */
+    long selectRsTrainingApprovalWithDetailsCount(RsTrainingApproval approval);
+
 } 

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

@@ -197,8 +197,8 @@ public class RsTrainingApprovalServiceImpl implements IRsTrainingApprovalService
             approval.setTenantId(LoginHelper.getTenantId());
         }
         
-        // 查询总数
-        long total = trainingApprovalMapper.selectCount(buildQueryWrapper(approval));
+        // 查询总数 - 使用与列表查询相同的条件
+        long total = trainingApprovalMapper.selectRsTrainingApprovalWithDetailsCount(approval);
         
         // 使用自定义分页查询获取列表数据
         List<RsTrainingApproval> list = trainingApprovalMapper.selectRsTrainingApprovalWithDetailsList(approval);

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

@@ -11,6 +11,16 @@
         <result property="userName" column="user_name"/>
         <result property="department" column="department"/>
         <result property="position" column="position"/>
+        <result property="currentTitle" column="current_title"/>
+        <result property="currentPosition" column="current_position"/>
+        <result property="workStartTime" column="work_start_time"/>
+        <result property="phone" column="phone"/>
+        <result property="organizerUnit" column="organizer_unit"/>
+        <result property="trainingType" column="training_type"/>
+        <result property="trainingFee" column="training_fee"/>
+        <result property="companions" column="companions"/>
+        <result property="trainingPurpose" column="training_purpose"/>
+        <result property="attachmentIds" column="attachment_ids"/>
         <result property="reason" column="reason"/>
         <result property="status" column="status"/>
         <result property="applyTime" column="apply_time"/>
@@ -31,8 +41,10 @@
 
     <sql id="selectRsTrainingApplicationVo">
         SELECT 
-            a.application_id, a.course_id, a.course_name as custom_course_name, a.user_id, a.user_name, a.department, a.position, a.reason, 
-            a.status, a.apply_time, a.current_approver, a.location, a.duration, a.start_time, a.end_time,
+            a.application_id, a.course_id, a.course_name as custom_course_name, a.user_id, a.user_name, a.department, a.position, 
+            a.current_title, a.current_position, a.work_start_time, a.phone, a.organizer_unit, 
+            a.training_type, a.training_fee, a.companions, a.training_purpose, a.attachment_ids,
+            a.reason, a.status, a.apply_time, a.current_approver, a.location, a.duration, a.start_time, a.end_time,
             a.tenant_id, a.create_dept, a.reject_time, a.create_by, a.create_time, a.update_by, a.update_time,
             CASE 
                 WHEN a.course_id = 0 THEN a.course_name 

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

@@ -37,9 +37,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="updateBy" column="update_by"/>
         <result property="updateTime" column="update_time"/>
         <result property="remark" column="remark"/>
-        <!-- 关联字段 -->
+        <!-- 关联字段 - 审批记录显示需要的申请信息 -->
         <result property="applicationUserName" column="application_user_name"/>
         <result property="courseTitle" column="course_title"/>
+        <!-- 新增详情显示需要的字段 -->
+        <result property="userName" column="user_name"/>
+        <result property="department" column="department"/>
+        <result property="currentTitle" column="current_title"/>
+        <result property="currentPosition" column="current_position"/>
+        <result property="workStartTime" column="work_start_time"/>
+        <result property="phone" column="phone"/>
+        <result property="organizerUnit" column="organizer_unit"/>
+        <result property="courseName" column="course_name"/>
+        <result property="location" column="location"/>
+        <result property="startTime" column="start_time"/>
+        <result property="endTime" column="end_time"/>
+        <result property="trainingType" column="training_type"/>
+        <result property="trainingFee" column="training_fee"/>
+        <result property="applyTime" column="apply_time"/>
+        <result property="companions" column="companions"/>
+        <result property="trainingPurpose" column="training_purpose"/>
+        <result property="reason" column="reason"/>
+        <result property="attachmentIds" column="attachment_ids"/>
     </resultMap>
 
     <sql id="selectRsTrainingApprovalVo">
@@ -81,8 +100,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             a.approval_id, a.application_id, a.approver_id, a.approver_name, 
             a.approval_level, a.status, a.approval_time, a.opinion, 
             a.tenant_id, a.create_by, a.create_time, a.update_by, a.update_time, a.remark,
+            -- 审批记录列表显示字段
             app.user_name as application_user_name,
-            COALESCE(app.course_name, c.title) as course_title
+            COALESCE(app.course_name, c.title) as course_title,
+            -- 详情弹窗显示字段(使用别名避免重复)
+            app.user_name as user_name,
+            app.department,
+            app.current_title,
+            app.current_position,
+            app.work_start_time,
+            app.phone,
+            app.organizer_unit,
+            app.course_name as course_name,
+            app.location,
+            app.start_time,
+            app.end_time,
+            app.training_type,
+            app.training_fee,
+            app.apply_time,
+            app.companions,
+            app.training_purpose,
+            app.reason,
+            app.attachment_ids
         FROM rs_training_approval a
         LEFT JOIN rs_training_application app ON a.application_id = app.application_id AND app.deleted = '0'
         LEFT JOIN rs_training_course c ON app.course_id = c.course_id AND c.deleted = '0'
@@ -121,4 +160,44 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ORDER BY a.approval_time DESC
     </select>
 
+    <!-- 查询带详细信息的审批记录数量(关联申请人和课程信息) -->
+    <select id="selectRsTrainingApprovalWithDetailsCount" parameterType="RsTrainingApproval" resultType="long">
+        SELECT COUNT(1)
+        FROM rs_training_approval a
+        LEFT JOIN rs_training_application app ON a.application_id = app.application_id AND app.deleted = '0'
+        LEFT JOIN rs_training_course c ON app.course_id = c.course_id AND c.deleted = '0'
+        <where>
+            a.deleted = '0'
+            <if test="applicationId != null">
+                AND a.application_id = #{applicationId}
+            </if>
+            <if test="approverId != null">
+                AND a.approver_id = #{approverId}
+            </if>
+            <if test="approverName != null and approverName != ''">
+                AND a.approver_name like concat('%', #{approverName}, '%')
+            </if>
+            <if test="approvalLevel != null">
+                AND a.approval_level = #{approvalLevel}
+            </if>
+            <if test="status != null and status != ''">
+                AND a.status IN 
+                <foreach collection="status.split(',')" item="statusItem" open="(" separator="," close=")">
+                    #{statusItem}
+                </foreach>
+            </if>
+            <if test="tenantId != null and tenantId != ''">
+                AND a.tenant_id = #{tenantId}
+            </if>
+            <!-- 支持按申请人姓名搜索 -->
+            <if test="applicationUserName != null and applicationUserName != ''">
+                AND app.user_name like concat('%', #{applicationUserName}, '%')
+            </if>
+            <!-- 支持按课程名称搜索 -->
+            <if test="courseTitle != null and courseTitle != ''">
+                AND (app.course_name like concat('%', #{courseTitle}, '%') OR c.title like concat('%', #{courseTitle}, '%'))
+            </if>
+        </where>
+    </select>
+
 </mapper> 

+ 28 - 4
web/src/api/rs/training.ts

@@ -70,16 +70,29 @@ export interface ApplicationVO {
   userName: string;
   department: string;
   position?: string;
+  currentTitle?: string;           // 现职称
+  currentPosition?: string;        // 现岗位
+  workStartTime?: string;          // 参加工作时间
+  phone?: string;                  // 联系电话
+  organizerUnit?: string;          // 主办单位
+  trainingType?: string;           // 培训类型
+  trainingFee?: number;            // 培训费用
+  companions?: string;             // 同行人员(JSON字符串)
+  trainingPurpose?: string;        // 培训目的
   reason?: string;
   status: string;
   applyTime: string;
   currentApprover?: string;
-  certificatePath?: string;
-  certificateNumber?: string;
+  location?: string;
+  duration?: number;
+  startTime?: string;
+  endTime?: string;
+  rejectTime?: string;             // 拒绝时间
+  attachmentIds?: string;          // 附件ID列表
   createTime: string;
   updateTime: string;
-  createBy: string;
-  updateBy: string;
+  createBy: number;                // 修正类型:string -> number
+  updateBy: number;                // 修正类型:string -> number
   remark: string;
 }
 
@@ -92,12 +105,23 @@ export interface ApplicationForm {
   userName?: string;
   department?: string;
   position?: string;
+  currentTitle?: string;           // 现职称
+  currentPosition?: string;        // 现岗位
+  workStartTime?: string;          // 参加工作时间
+  phone?: string;                  // 联系电话
+  organizerUnit?: string;          // 主办单位
+  trainingType?: string;           // 培训类型
+  trainingTypeOther?: string;      // 其他培训类型描述
+  trainingFee?: number;            // 培训费用
+  companions?: (string | number)[]; // 同行人员数组
+  trainingPurpose?: string;        // 培训目的
   reason?: string;
   status?: string;
   location?: string;
   duration?: number;
   startTime?: string;
   endTime?: string;
+  attachmentIds?: string[];        // 附件ID数组
   remark?: string;
 }
 

+ 47 - 52
web/src/views/bzrkh/teacherAssessment/submitDialog.vue

@@ -157,14 +157,10 @@
             </el-col>
             <el-col :span="8">
               <el-form-item>
-                <el-button 
-              :type="studentRecordsData ? 'success' : 'info'" 
-              :plain="!studentRecordsData" 
-              @click="handleViewStudentViolations"
-            >
-              <i :class="studentRecordsData ? 'el-icon-check' : 'el-icon-view'" style="margin-right: 4px;"></i>
-              {{ studentRecordsData ? '查看已加载的违规处分信息' : '学生违规处分信息查看' }}
-            </el-button>
+                <el-button :type="studentRecordsData ? 'success' : 'info'" :plain="!studentRecordsData" @click="handleViewStudentViolations">
+                  <i :class="studentRecordsData ? 'el-icon-check' : 'el-icon-view'" style="margin-right: 4px"></i>
+                  {{ studentRecordsData ? '查看已加载的违规处分信息' : '学生违规处分信息查看' }}
+                </el-button>
               </el-form-item>
             </el-col>
           </el-row>
@@ -382,27 +378,22 @@
         <!-- 违纪记录Tab -->
         <el-tab-pane label="违纪记录" name="discipline">
           <!-- 扣分汇总信息 -->
-          <el-row :gutter="20" style="margin-bottom: 16px;">
+          <el-row :gutter="20" style="margin-bottom: 16px">
             <el-col :span="16">
-              <el-alert 
-                :title="`违纪记录扣分汇总: ${calculateTotalDeduction()} 分 (生效记录: ${getEffectiveDisciplineRecords().length} 条)`" 
-                type="warning" 
+              <el-alert
+                :title="`违纪记录扣分汇总: ${calculateTotalDeduction()} 分 (生效记录: ${getEffectiveDisciplineRecords().length} 条)`"
+                type="warning"
                 :closable="false"
                 show-icon
               />
             </el-col>
-            <el-col :span="8" style="text-align: right;">
-              <el-button 
-                type="primary" 
-                icon="Refresh" 
-                @click="applyDeductionToClassScore"
-                :disabled="calculateTotalDeduction() === 0"
-              >
+            <el-col :span="8" style="text-align: right">
+              <el-button type="primary" icon="Refresh" @click="applyDeductionToClassScore" :disabled="calculateTotalDeduction() === 0">
                 重新同步到班级扣分
               </el-button>
             </el-col>
           </el-row>
-          
+
           <el-table :data="studentRecordsData?.disciplineRecords || []" border style="width: 100%" max-height="400" empty-text="暂无违纪记录">
             <el-table-column type="index" label="序号" width="60" align="center" />
             <el-table-column prop="studentNo" label="学号" min-width="120" align="center" />
@@ -417,10 +408,7 @@
             <el-table-column prop="disciplineContent" label="处分内容" min-width="200" :show-overflow-tooltip="true" />
             <el-table-column prop="scoreDeduction" label="扣分" min-width="80" align="center">
               <template #default="scope">
-                <el-tag 
-                  :type="scope.row.status === '1' ? 'danger' : 'info'" 
-                  size="small"
-                >
+                <el-tag :type="scope.row.status === '1' ? 'danger' : 'info'" size="small">
                   {{ scope.row.scoreDeduction || 0 }}
                 </el-tag>
               </template>
@@ -721,7 +709,7 @@ const loadAssessmentDetail = async () => {
       overallComment: monthlyScoreForm.overallComment
     });
     console.log('==================');
-    
+
     // 自动加载学生违规处分信息数据
     await autoLoadStudentRecords();
   } catch (error) {
@@ -746,7 +734,7 @@ const autoLoadStudentRecords = async () => {
   try {
     const res = await getClassStudentRecords(props.classId, props.planId);
     studentRecordsData.value = res.data;
-    
+
     // 自动同步违纪记录扣分到班级扣分字段(不显示提示消息)
     autoSyncDeductionToClassScore(false);
     console.log('✅ 学生违规处分数据自动加载完成');
@@ -778,7 +766,7 @@ const handleViewStudentViolations = async () => {
       studentRecordsData.value = res.data;
       studentRecordsVisible.value = true;
     }
-    
+
     // 手动查看时也重新同步一次扣分(显示提示消息)
     autoSyncDeductionToClassScore(true);
   } catch (error) {
@@ -795,7 +783,7 @@ const getEffectiveDisciplineRecords = () => {
     return [];
   }
   // 只统计状态为'1'(生效)的违纪记录
-  return studentRecordsData.value.disciplineRecords.filter(record => record.status === '1');
+  return studentRecordsData.value.disciplineRecords.filter((record) => record.status === '1');
 };
 
 /** 计算违纪记录总扣分 */
@@ -810,50 +798,57 @@ const calculateTotalDeduction = () => {
 /** 自动同步违纪记录扣分到班级扣分字段 */
 const autoSyncDeductionToClassScore = (showMessage = true) => {
   const totalDeduction = calculateTotalDeduction();
-  
+
   // 自动将汇总的扣分设置到班级扣分字段
   managementEffectivenessForm.classDeduction = totalDeduction;
   // 自动计算50%计
   calculateDeductionHalf();
-  
+
   if (totalDeduction > 0 && showMessage) {
     proxy?.$modal.msgSuccess(`已自动同步违纪记录扣分汇总 ${totalDeduction} 分到班级扣分字段`);
   }
 };
 
 // 监听学生违纪记录数据变化,自动同步扣分
-watch(() => studentRecordsData.value?.disciplineRecords, (newRecords) => {
-  if (newRecords && studentRecordsVisible.value) {
-    // 延迟执行,确保数据完全加载,但不显示提示消息
-    nextTick(() => {
-      autoSyncDeductionToClassScore(false);
-    });
-  }
-}, { deep: true });
+watch(
+  () => studentRecordsData.value?.disciplineRecords,
+  (newRecords) => {
+    if (newRecords && studentRecordsVisible.value) {
+      // 延迟执行,确保数据完全加载,但不显示提示消息
+      nextTick(() => {
+        autoSyncDeductionToClassScore(false);
+      });
+    }
+  },
+  { deep: true }
+);
 
 /** 将违纪记录扣分汇总应用到班级扣分字段 */
 const applyDeductionToClassScore = () => {
   const totalDeduction = calculateTotalDeduction();
-  
+
   if (totalDeduction === 0) {
     proxy?.$modal.msgWarning('当前没有生效的违纪记录扣分可以应用');
     return;
   }
 
   // 确认应用操作
-  proxy?.$modal.confirm(`确定要将违纪记录扣分汇总 ${totalDeduction} 分重新应用到班级扣分字段吗?`).then(() => {
-    // 将汇总的扣分设置到班级扣分字段
-    managementEffectivenessForm.classDeduction = totalDeduction;
-    // 自动计算50%计
-    calculateDeductionHalf();
-    
-    proxy?.$modal.msgSuccess(`已重新应用违纪记录扣分汇总 ${totalDeduction} 分到班级扣分字段`);
-    
-    // 关闭学生记录弹窗
-    studentRecordsVisible.value = false;
-  }).catch(() => {
-    // 用户取消操作
-  });
+  proxy?.$modal
+    .confirm(`确定要将违纪记录扣分汇总 ${totalDeduction} 分重新应用到班级扣分字段吗?`)
+    .then(() => {
+      // 将汇总的扣分设置到班级扣分字段
+      managementEffectivenessForm.classDeduction = totalDeduction;
+      // 自动计算50%计
+      calculateDeductionHalf();
+
+      proxy?.$modal.msgSuccess(`已重新应用违纪记录扣分汇总 ${totalDeduction} 分到班级扣分字段`);
+
+      // 关闭学生记录弹窗
+      studentRecordsVisible.value = false;
+    })
+    .catch(() => {
+      // 用户取消操作
+    });
 };
 
 /** 保存考核数据 */

+ 63 - 3
web/src/views/rs/index.vue

@@ -385,6 +385,18 @@
         </el-descriptions-item>
         <el-descriptions-item label="申请人数">{{ viewForm.applicantCount }}</el-descriptions-item>
         <el-descriptions-item label="浏览次数">{{ viewForm.viewCount }}</el-descriptions-item>
+        <el-descriptions-item label="应聘链接">
+          <el-link 
+            :href="`/recruitment-apply?jobId=${viewForm.jobId}`" 
+            target="_blank" 
+            type="primary" 
+            :underline="false"
+            style="font-size: 14px;"
+          >
+            点击前往应聘页面
+            <el-icon style="margin-left: 4px;"><Link /></el-icon>
+          </el-link>
+        </el-descriptions-item>
         <el-descriptions-item label="发布时间">
           {{ parseTime(viewForm.publishTime) || (viewForm.isActive === '0' ? parseTime(viewForm.createTime) || '长期有效' : '未发布') }}
         </el-descriptions-item>
@@ -508,7 +520,21 @@
           </el-descriptions-item>
           <el-descriptions-item label="申请时间">{{ parseTime(applicationDetail.applyTime) }}</el-descriptions-item>
           <el-descriptions-item label="联系电话">{{ applicationDetail.contactPhone }}</el-descriptions-item>
-          <el-descriptions-item label="联系邮箱">{{ applicationDetail.contactEmail }}</el-descriptions-item>
+          <el-descriptions-item label="联系邮箱">
+            <div style="display: flex; align-items: center; gap: 8px;">
+              <span>{{ applicationDetail.contactEmail }}</span>
+              <el-button 
+                v-if="applicationDetail.contactEmail"
+                size="small" 
+                type="primary" 
+                :icon="Message" 
+                @click="sendEmail(applicationDetail.contactEmail, applicationDetail.applicantName)"
+                style="margin-left: 8px;"
+              >
+                发送邮件
+              </el-button>
+            </div>
+          </el-descriptions-item>
           <el-descriptions-item label="学历">{{ applicationDetail.education || '暂无信息' }}</el-descriptions-item>
           <el-descriptions-item label="教育背景">{{ applicationDetail.educationBackground || '暂无信息' }}</el-descriptions-item>
           <el-descriptions-item label="工作经验" :span="2">{{ applicationDetail.experience || '暂无信息' }}</el-descriptions-item>
@@ -544,7 +570,21 @@
         <el-descriptions-item label="性别">{{ currentApplication?.gender || '未提供' }}</el-descriptions-item>
         <el-descriptions-item label="年龄">{{ currentApplication?.age || '未提供' }}</el-descriptions-item>
         <el-descriptions-item label="联系电话">{{ currentApplication?.contactPhone }}</el-descriptions-item>
-        <el-descriptions-item label="邮箱地址">{{ currentApplication?.contactEmail }}</el-descriptions-item>
+        <el-descriptions-item label="邮箱地址">
+          <div style="display: flex; align-items: center; gap: 8px;">
+            <span>{{ currentApplication?.contactEmail }}</span>
+            <el-button 
+              v-if="currentApplication?.contactEmail"
+              size="small" 
+              type="primary" 
+              :icon="Message" 
+              @click="sendEmail(currentApplication?.contactEmail, currentApplication?.applicantName)"
+              style="margin-left: 8px;"
+            >
+              发送邮件
+            </el-button>
+          </div>
+        </el-descriptions-item>
         <el-descriptions-item label="申请状态">
           <el-tag
             :type="
@@ -681,7 +721,7 @@ import { parseTime } from '@/utils/ruoyi';
 import type { JobVO, JobForm, ApplicationVO } from '@/api/rs/job';
 import { listDept } from '@/api/system/dept';
 import type { DeptVO } from '@/api/system/dept/types';
-import { Document } from '@element-plus/icons-vue';
+import { Document, Link, Message } from '@element-plus/icons-vue';
 import { getToken } from '@/utils/auth';
 import jsPDF from 'jspdf';
 import html2canvas from 'html2canvas';
@@ -1484,6 +1524,26 @@ function getStatusType(value: string): 'primary' | 'success' | 'info' | 'warning
   return (option?.type as 'primary' | 'success' | 'info' | 'warning' | 'danger') || 'info';
 }
 
+/** 发送邮件 */
+function sendEmail(email: string, applicantName: string) {
+  if (!email) {
+    ElMessage.warning('邮箱地址为空');
+    return;
+  }
+  
+  const subject = encodeURIComponent(`关于您的求职申请 - ${applicantName}`);
+  const body = encodeURIComponent(`亲爱的 ${applicantName}:\n\n感谢您对我们的关注和求职申请。\n\n我们将仔细审核您的申请材料,如有需要会及时与您联系。\n\n祝您工作顺利!\n\n此致\n敬礼\n\nHR部门`);
+  
+  const mailtoUrl = `mailto:${email}?subject=${subject}&body=${body}`;
+  
+  try {
+    window.open(mailtoUrl);
+    ElMessage.success('已打开邮件客户端');
+  } catch (error) {
+    ElMessage.error('打开邮件客户端失败,请检查系统设置');
+  }
+}
+
 /** 选项卡切换 */
 function handleTabClick() {
   if (activeTab.value === 'application') {

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 795 - 75
web/src/views/rs/training/index.vue


+ 688 - 208
web/src/views/rs/trainingApproval/index.vue

@@ -11,10 +11,10 @@
                 <el-form ref="applicationQueryForm" :model="applicationQueryParams" label-width="100px">
                   <el-row :gutter="20">
                     <el-col :span="6">
-                      <el-form-item label="课程名称" prop="courseName">
+                      <el-form-item label="主办单位" prop="organizerUnit">
                         <el-input
-                          v-model="applicationQueryParams.courseName"
-                          placeholder="请输入课程名称"
+                          v-model="applicationQueryParams.organizerUnit"
+                          placeholder="请输入主办单位"
                           clearable
                           @keyup.enter="handleApplicationQuery"
                         />
@@ -78,9 +78,15 @@
               @row-dblclick="handleViewApplicant"
             >
               <el-table-column type="selection" width="55" align="center" />
-              <el-table-column label="课程名称" align="center" prop="courseName" :show-overflow-tooltip="true" />
+              <el-table-column label="主办单位" align="center" prop="organizerUnit" :show-overflow-tooltip="true" />
               <el-table-column label="培训地点" align="center" prop="location" />
+              <el-table-column label="培训类型" align="center" prop="trainingType" />
               <el-table-column label="申请人" align="center" prop="userName" />
+              <el-table-column label="同行人" align="center" prop="companions" width="150">
+                <template #default="scope">
+                  <span>{{ getCompanionNames(scope.row.companions) || '无' }}</span>
+                </template>
+              </el-table-column>
               <el-table-column label="申请时间" align="center" prop="applyTime" width="180">
                 <template #default="scope">
                   <span>{{ parseTime(scope.row.applyTime) }}</span>
@@ -190,9 +196,32 @@
               @row-dblclick="handleViewApprovalRecord"
             >
               <el-table-column type="selection" width="55" align="center" />
-              <el-table-column label="课程名称" align="center" prop="courseTitle" :show-overflow-tooltip="true" />
+              <el-table-column label="主办单位" align="center" prop="organizerUnit" :show-overflow-tooltip="true">
+                <template #default="scope">
+                  <span>{{ scope.row.organizerUnit || scope.row.courseName || '暂无' }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="培训地点" align="center" prop="location">
+                <template #default="scope">
+                  <span>{{ scope.row.location || '暂无' }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="培训类型" align="center" prop="trainingType">
+                <template #default="scope">
+                  <span>{{ scope.row.trainingType || '暂无' }}</span>
+                </template>
+              </el-table-column>
               <el-table-column label="申请人" align="center" prop="applicationUserName" />
-              <el-table-column label="审批人" align="center" prop="approverName" />
+              <el-table-column label="同行人" align="center" prop="companions" width="150">
+                <template #default="scope">
+                  <span>{{ getCompanionNames(scope.row.companions) || '无' }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="申请时间" align="center" prop="applyTime" width="180">
+                <template #default="scope">
+                  <span>{{ scope.row.applyTime ? parseTime(scope.row.applyTime) : '暂无' }}</span>
+                </template>
+              </el-table-column>
               <el-table-column label="审批时间" align="center" prop="approvalTime" width="180">
                 <template #default="scope">
                   <span>{{ parseTime(scope.row.approvalTime) }}</span>
@@ -231,105 +260,82 @@
 
     <!-- 申请详情对话框 -->
     <el-dialog title="申请详情" v-model="applicationDetailOpen" width="800px" append-to-body>
-      <el-form :model="applicationDetail" label-width="100px" readonly>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="课程名称">
-              <el-input v-model="applicationDetail.courseName" readonly />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="申请人">
-              <el-input v-model="applicationDetail.userName" readonly />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="申请人部门">
-              <el-input v-model="applicationDetail.department" readonly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="申请时间">
-              <el-input v-model="applicationDetail.applyTime" readonly />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="状态">
-              <el-tag
-                :type="
-                  applicationDetail.status === '0'
-                    ? 'warning'
-                    : applicationDetail.status === '1'
-                      ? 'success'
-                      : applicationDetail.status === '2'
-                        ? 'danger'
-                        : 'info'
-                "
-              >
-                {{ getStatusLabel(applicationDetail.status) }}
-              </el-tag>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="开始时间">
-              <el-input v-model="applicationDetail.startTime" readonly />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="结束时间">
-              <el-input v-model="applicationDetail.endTime" readonly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="学时">
-              <el-input :value="applicationDetail.duration ? applicationDetail.duration + ' 小时' : ''" readonly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="申请理由">
-              <el-input type="textarea" v-model="applicationDetail.reason" readonly :rows="3" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row v-if="applicationDetail.adminRemark">
-          <el-col :span="24">
-            <el-form-item label="审批意见">
-              <el-input type="textarea" v-model="applicationDetail.adminRemark" readonly :rows="3" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 审批历史记录 -->
-        <el-row v-if="approvalHistoryList.length > 0">
-          <el-col :span="24">
-            <el-divider content-position="center">审批历史</el-divider>
-            <el-table :data="approvalHistoryList" style="width: 100%" border stripe>
-              <el-table-column label="审批人" align="center" prop="approverName" />
-              <el-table-column label="审批时间" align="center" width="160">
-                <template #default="scope">
-                  <span>{{ parseTime(scope.row.approvalTime) }}</span>
-                </template>
-              </el-table-column>
-              <el-table-column label="审批结果" align="center" width="120">
-                <template #default="scope">
-                  <el-tag :type="scope.row.status === '1' ? 'success' : 'danger'">
-                    {{ scope.row.status === '1' ? '通过' : '拒绝' }}
-                  </el-tag>
-                </template>
-              </el-table-column>
-              <el-table-column label="审批意见" align="center" prop="opinion" :show-overflow-tooltip="true" />
-            </el-table>
-          </el-col>
-        </el-row>
-      </el-form>
+      <el-descriptions :column="2" border>
+        <!-- 申请人基本信息 -->
+        <el-descriptions-item label="申请人">{{ applicationDetail.userName }}</el-descriptions-item>
+        <el-descriptions-item label="所在部门(系部)">{{ applicationDetail.department }}</el-descriptions-item>
+        <el-descriptions-item label="现职称">{{ applicationDetail.currentTitle || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="现岗位">{{ applicationDetail.currentPosition || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="参加工作时间">{{ parseTime(applicationDetail.workStartTime) || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="联系电话">{{ applicationDetail.phone || '暂无' }}</el-descriptions-item>
+        
+        <!-- 培训情况信息 -->
+        <el-descriptions-item label="主办单位">{{ applicationDetail.organizerUnit || applicationDetail.courseName || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="培训地点">{{ applicationDetail.location }}</el-descriptions-item>
+        <el-descriptions-item label="开始时间">{{ parseTime(applicationDetail.startTime) }}</el-descriptions-item>
+        <el-descriptions-item label="结束时间">{{ parseTime(applicationDetail.endTime) }}</el-descriptions-item>
+        <el-descriptions-item label="培训类型">{{ applicationDetail.trainingType || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="培训费用">{{ applicationDetail.trainingFee ? `${applicationDetail.trainingFee}元` : '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="申请状态">
+          <el-tag
+            :type="
+              applicationDetail.status === '0'
+                ? 'warning'
+                : applicationDetail.status === '1'
+                  ? 'success'
+                  : applicationDetail.status === '2'
+                    ? 'danger'
+                    : 'info'
+            "
+          >
+            {{ getStatusLabel(applicationDetail.status) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="申请时间">{{ parseTime(applicationDetail.applyTime) }}</el-descriptions-item>
+        <el-descriptions-item label="同行人" :span="2">{{ getCompanionNames(applicationDetail.companions) || '无' }}</el-descriptions-item>
+        <el-descriptions-item label="培训目的" :span="2">{{ applicationDetail.trainingPurpose || applicationDetail.reason || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="2">{{ applicationDetail.remark || '无' }}</el-descriptions-item>
+
+        <!-- 显示拒绝原因,如果申请被拒绝 -->
+        <el-descriptions-item v-if="applicationDetail.status === '2'" label="拒绝原因" :span="2" class="reject-reason">
+          <div class="reject-info">
+            <el-alert type="error" :closable="false">
+              <template #title>
+                <div class="reject-title">申请已被拒绝</div>
+              </template>
+              <div class="reject-content">{{ applicationDetail.adminRemark || '未提供拒绝原因' }}</div>
+            </el-alert>
+            <div class="reject-action">
+              <el-button type="primary" size="small" @click="handleViewHistory(applicationDetail)">查看完整审批历史</el-button>
+            </div>
+          </div>
+        </el-descriptions-item>
+      </el-descriptions>
+
+      <!-- 申请附件显示区域 -->
+      <div v-if="applicationDetailAttachmentList.length > 0" class="attachment-section" style="margin-top: 20px;">
+        <div class="section-title" style="margin-bottom: 15px; padding: 10px; border-left: 4px solid #409eff; background-color: #f5f7fa;">
+          <el-icon style="margin-right: 8px;"><Paperclip /></el-icon>
+          <strong>申请附件</strong>
+        </div>
+        <el-table :data="applicationDetailAttachmentList" border size="small">
+          <el-table-column label="序号" type="index" width="60" align="center" />
+          <el-table-column label="文件名称" prop="name" align="left" show-overflow-tooltip />
+          <el-table-column label="上传时间" prop="uploadTime" width="180" align="center">
+            <template #default="scope">
+              {{ parseTime(scope.row.uploadTime) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="100" align="center">
+            <template #default="scope">
+              <el-button type="primary" link icon="Download" @click="handleDownloadApplicationAttachment(scope.row)" size="small">
+                下载
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="applicationDetailOpen = false">关闭</el-button>
@@ -339,6 +345,34 @@
       </template>
     </el-dialog>
 
+    <!-- 审批历史对话框 -->
+    <el-dialog title="审批历史记录" v-model="approvalHistoryDialogOpen" width="700px" append-to-body>
+      <el-timeline>
+        <el-timeline-item
+          v-for="(approval, index) in approvalHistoryList"
+          :key="index"
+          :timestamp="parseTime(approval.approvalTime)"
+          :type="approval.status === '1' ? 'success' : approval.status === '2' ? 'danger' : 'primary'"
+        >
+          <div class="approval-history-item">
+            <div class="approval-header">
+              <span class="approver-name">{{ approval.approverName }}</span>
+              <el-tag :type="approval.status === '0' ? 'info' : approval.status === '1' ? 'success' : 'danger'" size="small">
+                {{ approval.status === '0' ? '待审核' : approval.status === '1' ? '已通过' : '已拒绝' }}
+              </el-tag>
+            </div>
+            <div class="approval-opinion">{{ approval.opinion || '无审批意见' }}</div>
+          </div>
+        </el-timeline-item>
+      </el-timeline>
+      <div v-if="approvalHistoryList.length === 0" class="no-data">暂无审批记录</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="approvalHistoryDialogOpen = false">关闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
     <!-- 审批对话框 -->
     <el-dialog :title="approvalType === 'approve' ? '审批通过' : '审批拒绝'" v-model="approvalDialogOpen" width="500px">
       <el-form :model="approvalForm" label-width="80px">
@@ -356,102 +390,103 @@
 
     <!-- 审批记录详情对话框 -->
     <el-dialog title="审批记录详情" v-model="approvalRecordDetailOpen" width="800px" append-to-body>
-      <el-form :model="approvalRecordDetail" label-width="100px" readonly>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="课程名称">
-              <el-input v-model="approvalRecordDetail.courseName" readonly />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="申请人">
-              <el-input v-model="approvalRecordDetail.userName" readonly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="申请时间">
-              <el-input :value="parseTime(approvalRecordDetail.applyTime)" readonly />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="审批时间">
-              <el-input :value="parseTime(approvalRecordDetail.approvalTime)" readonly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="审批结果">
-              <el-tag :type="approvalRecordDetail.status === '1' ? 'success' : approvalRecordDetail.status === '2' ? 'danger' : 'info'">
-                {{ getStatusLabel(approvalRecordDetail.status) }}
+      <el-descriptions :column="2" border>
+        <!-- 申请人基本信息 -->
+        <el-descriptions-item label="申请人">{{ approvalRecordDetail.userName }}</el-descriptions-item>
+        <el-descriptions-item label="所在部门(系部)">{{ approvalRecordDetail.department }}</el-descriptions-item>
+        <el-descriptions-item label="现职称">{{ approvalRecordDetail.currentTitle || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="现岗位">{{ approvalRecordDetail.currentPosition || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="参加工作时间">{{ parseTime(approvalRecordDetail.workStartTime) || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="联系电话">{{ approvalRecordDetail.phone || '暂无' }}</el-descriptions-item>
+        
+        <!-- 培训情况信息 -->
+        <el-descriptions-item label="主办单位">{{ approvalRecordDetail.organizerUnit || approvalRecordDetail.courseName || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="培训地点">{{ approvalRecordDetail.location }}</el-descriptions-item>
+        <el-descriptions-item label="开始时间">{{ parseTime(approvalRecordDetail.startTime) }}</el-descriptions-item>
+        <el-descriptions-item label="结束时间">{{ parseTime(approvalRecordDetail.endTime) }}</el-descriptions-item>
+        <el-descriptions-item label="培训类型">{{ approvalRecordDetail.trainingType || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="培训费用">{{ approvalRecordDetail.trainingFee ? `${approvalRecordDetail.trainingFee}元` : '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="审批状态">
+          <el-tag
+            :type="
+              approvalRecordDetail.status === '0'
+                ? 'warning'
+                : approvalRecordDetail.status === '1'
+                  ? 'success'
+                  : approvalRecordDetail.status === '2'
+                    ? 'danger'
+                    : 'info'
+            "
+          >
+            {{ getStatusLabel(approvalRecordDetail.status) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="申请时间">{{ parseTime(approvalRecordDetail.applyTime) }}</el-descriptions-item>
+        <el-descriptions-item label="同行人" :span="2">{{ getCompanionNames(approvalRecordDetail.companions) || '无' }}</el-descriptions-item>
+        <el-descriptions-item label="培训目的" :span="2">{{ approvalRecordDetail.trainingPurpose || approvalRecordDetail.reason || '暂无' }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="2">{{ approvalRecordDetail.remark || '无' }}</el-descriptions-item>
+
+        <!-- 显示拒绝原因,如果申请被拒绝 -->
+        <el-descriptions-item v-if="approvalRecordDetail.status === '2'" label="拒绝原因" :span="2" class="reject-reason">
+          <div class="reject-info">
+            <el-alert type="error" :closable="false">
+              <template #title>
+                <div class="reject-title">申请已被拒绝</div>
+              </template>
+              <div class="reject-content">{{ approvalRecordDetail.adminRemark || '未提供拒绝原因' }}</div>
+            </el-alert>
+          </div>
+        </el-descriptions-item>
+      </el-descriptions>
+
+      <!-- 申请附件显示区域 -->
+      <div v-if="approvalRecordDetailAttachmentList.length > 0" class="attachment-section" style="margin-top: 20px;">
+        <div class="section-title" style="margin-bottom: 15px; padding: 10px; border-left: 4px solid #409eff; background-color: #f5f7fa;">
+          <el-icon style="margin-right: 8px;"><Paperclip /></el-icon>
+          <strong>申请附件</strong>
+        </div>
+        <el-table :data="approvalRecordDetailAttachmentList" border size="small">
+          <el-table-column label="序号" type="index" width="60" align="center" />
+          <el-table-column label="文件名称" prop="name" align="left" show-overflow-tooltip />
+          <el-table-column label="上传时间" prop="uploadTime" width="180" align="center">
+            <template #default="scope">
+              {{ parseTime(scope.row.uploadTime) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="100" align="center">
+            <template #default="scope">
+              <el-button type="primary" link icon="Download" @click="handleDownloadApprovalRecordAttachment(scope.row)" size="small">
+                下载
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <!-- 审批历史记录 -->
+      <div v-if="approvalHistoryList.length > 0" style="margin-top: 20px;">
+        <div class="section-title" style="margin-bottom: 15px; padding: 10px; border-left: 4px solid #409eff; background-color: #f5f7fa;">
+          <el-icon style="margin-right: 8px;"><Clock /></el-icon>
+          <strong>审批历史</strong>
+        </div>
+        <el-table :data="approvalHistoryList" style="width: 100%" border stripe>
+          <el-table-column label="审批人" align="center" prop="approverName" />
+          <el-table-column label="审批时间" align="center" width="160">
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.approvalTime) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="审批结果" align="center" width="120">
+            <template #default="scope">
+              <el-tag :type="scope.row.status === '1' ? 'success' : 'danger'">
+                {{ scope.row.status === '1' ? '通过' : '拒绝' }}
               </el-tag>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="部门">
-              <el-input v-model="approvalRecordDetail.department" readonly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="开始时间">
-              <el-input v-model="approvalRecordDetail.startTime" readonly />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="结束时间">
-              <el-input v-model="approvalRecordDetail.endTime" readonly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="学时">
-              <el-input :value="approvalRecordDetail.duration ? approvalRecordDetail.duration + ' 小时' : ''" readonly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="申请理由">
-              <el-input type="textarea" v-model="approvalRecordDetail.reason" readonly :rows="3" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="审批意见">
-              <el-input type="textarea" v-model="approvalRecordDetail.adminRemark" readonly :rows="3" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 审批历史记录 -->
-        <el-row v-if="approvalHistoryList.length > 0">
-          <el-col :span="24">
-            <el-divider content-position="center">审批历史</el-divider>
-            <el-table :data="approvalHistoryList" style="width: 100%" border stripe>
-              <el-table-column label="审批人" align="center" prop="approverName" />
-              <el-table-column label="审批时间" align="center" width="160">
-                <template #default="scope">
-                  <span>{{ parseTime(scope.row.approvalTime) }}</span>
-                </template>
-              </el-table-column>
-              <el-table-column label="审批结果" align="center" width="120">
-                <template #default="scope">
-                  <el-tag :type="scope.row.status === '1' ? 'success' : 'danger'">
-                    {{ scope.row.status === '1' ? '通过' : '拒绝' }}
-                  </el-tag>
-                </template>
-              </el-table-column>
-              <el-table-column label="审批意见" align="center" prop="opinion" :show-overflow-tooltip="true" />
-            </el-table>
-          </el-col>
-        </el-row>
-      </el-form>
+            </template>
+          </el-table-column>
+          <el-table-column label="审批意见" align="center" prop="opinion" :show-overflow-tooltip="true" />
+        </el-table>
+      </div>
+
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="approvalRecordDetailOpen = false">关闭</el-button>
@@ -554,7 +589,8 @@
 
 <script setup lang="ts" name="TrainingApproval">
 import { reactive, ref, onMounted } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
+import { Paperclip, Clock } from '@element-plus/icons-vue';
 import { parseTime } from '@/utils/ruoyi';
 import {
   listTrainingApplication,
@@ -567,6 +603,8 @@ import {
   listAllApprovalRecords,
   processTrainingApproval
 } from '@/api/rs/training';
+import { listClassTeacherCounselorRel } from '@/api/xg/classTeacherCounselorRel';
+import request from '@/utils/request';
 
 // 动画配置
 const animate = reactive({
@@ -588,7 +626,7 @@ const applicationIds = ref<Array<string | number>>([]);
 const applicationQueryParams = reactive({
   pageNum: 1,
   pageSize: 10,
-  courseName: '',
+  organizerUnit: '',
   location: '',
   status: '',
   excludeStatus: '1', // 默认排除已通过状态的数据
@@ -653,6 +691,18 @@ const approvalRecordDetail = reactive<any>({
   userId: 0,
   userName: '',
   department: '',
+  currentTitle: '',
+  currentPosition: '',
+  workStartTime: '',
+  phone: '',
+  organizerUnit: '',
+  location: '',
+  trainingType: '',
+  trainingFee: '',
+  companions: '',
+  trainingPurpose: '',
+  startTime: '',
+  endTime: '',
   status: '0',
   applyTime: '',
   approvalTime: '',
@@ -661,6 +711,7 @@ const approvalRecordDetail = reactive<any>({
   adminRemark: '',
   certificatePath: ''
 });
+const approvalRecordDetailAttachmentList = ref<any[]>([]);
 
 // 审批历史变量
 const approvalHistoryDialogOpen = ref(false);
@@ -738,7 +789,7 @@ const resetApplicationQuery = () => {
   Object.assign(applicationQueryParams, {
     pageNum: 1,
     pageSize: 10,
-    courseName: '',
+    organizerUnit: '',
     location: '',
     status: '',
     excludeStatus: '1', // 保持排除已通过状态的设置
@@ -755,6 +806,11 @@ const handleApplicationSelectionChange = (selection: any[]) => {
 // 查看申请详情
 const handleViewApplicant = async (row: any) => {
   try {
+    // 确保教师列表已加载(用于显示同行人姓名)
+    if (teacherList.value.length === 0) {
+      await loadTeacherListForDisplay();
+    }
+
     const response = await getTrainingApplication(row.applicationId);
     Object.assign(applicationDetail, response.data);
 
@@ -762,6 +818,9 @@ const handleViewApplicant = async (row: any) => {
     const historyResponse = await getTrainingApprovalHistory(row.applicationId);
     approvalHistoryList.value = historyResponse.data;
 
+    // 加载申请详情的附件数据
+    await handleApplicationDetailAttachmentData(response.data.attachmentIds);
+
     applicationDetailOpen.value = true;
   } catch (error) {
     console.error('获取申请详情失败:', error);
@@ -833,17 +892,56 @@ const getApprovalRecordList = async () => {
     // 获取所有审批记录(不限于当前用户审批过的)
     const queryParams = {
       ...approvalRecordQueryParams,
-      status: '1,2', // 获取已通过和已拒绝的记录
+      // status: '1,2', // 暂时注释掉状态过滤,获取所有记录
       includeApprovedApplications: true // 确保包含已通过状态的培训申请数据
     };
+    
+    console.log('开始获取审批记录列表,查询参数:', queryParams);
     const response = await listAllApprovalRecords(queryParams);
-    approvalRecordList.value = response.rows;
-    approvalRecordTotal.value = response.total;
-
-    console.log('获取审批记录列表成功:', response);
+    
+    if (response && response.rows) {
+      approvalRecordList.value = response.rows;
+      approvalRecordTotal.value = response.total || 0;
+      console.log('获取审批记录列表成功,数据条数:', response.rows.length, '总数:', response.total);
+      console.log('审批记录数据详情:', response.rows);
+      
+      // 详细检查前3条记录的字段
+      if (response.rows.length > 0) {
+        console.log('=== 审批记录字段详细检查 ===');
+        response.rows.slice(0, 3).forEach((record, index) => {
+          console.log(`--- 记录 ${index + 1} ---`);
+          console.log('applicationId:', record.applicationId);
+          console.log('organizerUnit:', record.organizerUnit, '(类型:', typeof record.organizerUnit, ')');
+          console.log('location:', record.location, '(类型:', typeof record.location, ')');
+          console.log('trainingType:', record.trainingType, '(类型:', typeof record.trainingType, ')');
+          console.log('companions:', record.companions, '(类型:', typeof record.companions, ')');
+          console.log('courseName:', record.courseName);
+          console.log('courseTitle:', record.courseTitle);
+          console.log('applyTime:', record.applyTime);
+          console.log('applicationUserName:', record.applicationUserName);
+          console.log('approvalTime:', record.approvalTime);
+          console.log('status:', record.status);
+          
+          // 检查字段是否为空字符串或null
+          const targetFields = ['organizerUnit', 'location', 'trainingType', 'companions'];
+          const emptyFields = targetFields.filter(field => !record[field]);
+          const hasDataFields = targetFields.filter(field => record[field]);
+          
+          console.log('空字段:', emptyFields);
+          console.log('有数据字段:', hasDataFields);
+          console.log('完整对象键:', Object.keys(record));
+        });
+      }
+    } else {
+      approvalRecordList.value = [];
+      approvalRecordTotal.value = 0;
+      console.warn('获取审批记录列表响应格式异常:', response);
+    }
   } catch (error) {
     console.error('获取审批记录列表失败:', error);
     approvalRecordList.value = [];
+    approvalRecordTotal.value = 0;
+    ElMessage.error('获取审批记录列表失败,请稍后重试');
   } finally {
     approvalRecordLoading.value = false;
   }
@@ -888,6 +986,16 @@ const handleViewApprovalRecord = async (row: any) => {
       userId: applicationData.userId,
       userName: applicationData.userName,
       department: applicationData.department, // 从申请数据获取部门
+      currentTitle: applicationData.currentTitle, // 现职称
+      currentPosition: applicationData.currentPosition, // 现岗位
+      workStartTime: applicationData.workStartTime, // 参加工作时间
+      phone: applicationData.phone, // 联系电话
+      organizerUnit: applicationData.organizerUnit, // 主办单位
+      location: applicationData.location, // 培训地点
+      trainingType: applicationData.trainingType, // 培训类型
+      trainingFee: applicationData.trainingFee, // 培训费用
+      companions: applicationData.companions, // 同行人
+      trainingPurpose: applicationData.trainingPurpose, // 培训目的
       status: row.status, // 使用审批记录的状态
       applyTime: applicationData.applyTime, // 从申请数据获取申请时间
       approvalTime: row.approvalTime, // 从审批记录获取审批时间
@@ -901,6 +1009,9 @@ const handleViewApprovalRecord = async (row: any) => {
       duration: applicationData.duration // 培训学时
     });
 
+    // 处理申请附件数据
+    await handleApprovalRecordDetailAttachmentData(applicationData.attachmentIds);
+
     // 获取审批历史记录
     const historyResponse = await getTrainingApprovalHistory(row.applicationId);
     approvalHistoryList.value = historyResponse.data;
@@ -1064,17 +1175,328 @@ const submitReapplication = async () => {
 
 // 子选项卡切换
 const handleTrainingSubTabClick = (tab: any) => {
+  console.log('切换到选项卡:', tab.name);
   if (tab.name === 'application') {
+    console.log('切换到培训申请选项卡,刷新申请列表');
     getApplicationList();
   } else if (tab.name === 'approvalRecord') {
+    console.log('切换到审批记录选项卡,刷新审批记录列表');
     getApprovalRecordList();
   }
 };
 
+// 附件相关变量
+const applicationDetailAttachmentList = ref<any[]>([]);
+
+// 教师列表(用于同行人显示)
+const teacherList = ref<any[]>([]);
+
+/** 异步加载教师列表用于详情显示 */
+async function loadTeacherListForDisplay() {
+  try {
+    const response = await listClassTeacherCounselorRel({});
+    const teachers = response.rows || [];
+    teacherList.value = teachers.map((item: any) => ({
+      teacherId: item.teacherId,
+      teacherName: item.teacherName,
+      teacherType: item.teacherType
+    }));
+    console.log('教师列表加载完成:', teacherList.value.length, '位教师');
+  } catch (error) {
+    console.error('获取教师列表失败:', error);
+    teacherList.value = [];
+  }
+}
+
+/** 获取同行人姓名列表 */
+function getCompanionNames(companions: (string | number)[] | string | null | undefined): string {
+  if (!companions) {
+    return '';
+  }
+  
+  let companionArray: (string | number)[] = [];
+  
+  // 如果是字符串,尝试解析JSON
+  if (typeof companions === 'string') {
+    try {
+      companionArray = JSON.parse(companions);
+      if (!Array.isArray(companionArray)) {
+        return '';
+      }
+    } catch (e) {
+      // 如果JSON解析失败,可能是逗号分隔的字符串
+      companionArray = companions.split(',').map(id => id.trim()).filter(id => id);
+    }
+  } else if (Array.isArray(companions)) {
+    companionArray = companions;
+  }
+  
+  if (companionArray.length === 0) {
+    return '';
+  }
+  
+  // 如果教师列表还未加载,显示"加载中..."
+  if (teacherList.value.length === 0) {
+    return '加载中...';
+  }
+  
+  return companionArray.map(teacherId => {
+    // 确保teacherId转换为正确的类型进行比较
+    const teacher = teacherList.value.find(t => 
+      String(t.teacherId) === String(teacherId) || 
+      Number(t.teacherId) === Number(teacherId)
+    );
+    return teacher ? teacher.teacherName : `未知教师(ID:${teacherId})`;
+  }).join('、');
+}
+
+/** 查看审批历史 */
+async function handleViewHistory(row: any) {
+  try {
+    ElMessage.success('正在加载审批历史...');
+    const response = await getTrainingApprovalHistory(row.applicationId);
+    approvalHistoryList.value = response.data || [];
+    approvalHistoryDialogOpen.value = true;
+  } catch (error) {
+    console.error('获取审批历史失败:', error);
+    ElMessage.error('获取审批历史失败');
+  }
+}
+
+/** 下载申请附件 */
+async function handleDownloadApplicationAttachment(file: any) {
+  if (!file.ossId) {
+    ElMessage.error('无法下载文件:文件ID不存在');
+    return;
+  }
+
+  const loading = ElLoading.service({
+    lock: true,
+    text: '正在下载文件...',
+    background: 'rgba(0, 0, 0, 0.7)'
+  });
+
+  try {
+    console.log('开始下载申请附件:', file);
+    
+    // 通过OSS下载API获取文件二进制数据
+    const response = await request({
+      url: `/resource/oss/download/${file.ossId}`,
+      method: 'get',
+      responseType: 'blob',
+      timeout: 300000 // 300秒超时
+    }) as Blob;
+
+    // 验证响应数据
+    if (!response) {
+      throw new Error('响应数据为空');
+    }
+
+    // 创建Blob URL(响应拦截器对blob类型直接返回blob数据)
+    const blob = response;
+    const url = window.URL.createObjectURL(blob);
+
+    // 创建下载链接
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = file.name || '申请附件';
+    link.style.display = 'none';
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+
+    // 释放Blob URL
+    window.URL.revokeObjectURL(url);
+    loading.close();
+    
+    console.log('下载申请附件成功:', file.name);
+    ElMessage.success('文件下载成功');
+  } catch (error) {
+    console.error('下载申请附件失败:', error);
+    loading.close();
+    ElMessage.error('下载文件失败,请稍后重试');
+  }
+}
+
+/** 下载审批记录附件 */
+async function handleDownloadApprovalRecordAttachment(file: any) {
+  if (!file.ossId) {
+    ElMessage.error('无法下载文件:文件ID不存在');
+    return;
+  }
+
+  const loading = ElLoading.service({
+    lock: true,
+    text: '正在下载文件...',
+    background: 'rgba(0, 0, 0, 0.7)'
+  });
+
+  try {
+    console.log('开始下载审批记录附件:', file);
+    
+    // 通过OSS下载API获取文件二进制数据
+    const response = await request({
+      url: `/resource/oss/download/${file.ossId}`,
+      method: 'get',
+      responseType: 'blob',
+      timeout: 300000 // 300秒超时
+    }) as Blob;
+
+    // 验证响应数据
+    if (!response) {
+      throw new Error('响应数据为空');
+    }
+
+    // 创建Blob URL(响应拦截器对blob类型直接返回blob数据)
+    const blob = response;
+    const url = window.URL.createObjectURL(blob);
+
+    // 创建下载链接
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = file.name || '审批记录附件';
+    link.style.display = 'none';
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+
+    // 释放Blob URL
+    window.URL.revokeObjectURL(url);
+    loading.close();
+    
+    console.log('下载审批记录附件成功:', file.name);
+    ElMessage.success('文件下载成功');
+  } catch (error) {
+    console.error('下载审批记录附件失败:', error);
+    loading.close();
+    ElMessage.error('下载文件失败,请稍后重试');
+  }
+}
+
+/** 处理申请详情的附件数据 */
+async function handleApplicationDetailAttachmentData(attachmentIds: string | null | undefined) {
+  try {
+    if (!attachmentIds || !attachmentIds.toString().trim()) {
+      console.log('申请详情没有附件数据,清空附件列表');
+      applicationDetailAttachmentList.value = [];
+      return;
+    }
+
+    console.log('开始处理申请详情附件数据:', attachmentIds);
+
+    const ossIdsArray = attachmentIds
+      .toString()
+      .split(',')
+      .map((id) => id.trim())
+      .filter((id) => id);
+
+    if (ossIdsArray.length === 0) {
+      applicationDetailAttachmentList.value = [];
+      return;
+    }
+
+    const response = await request({
+      url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
+      method: 'get'
+    });
+
+    if (response.code === 200 && response.data) {
+      console.log('获取到的申请附件信息:', response.data);
+
+      const attachmentFiles = response.data.map((item: any, index: number) => ({
+        uid: `application_detail_${index}_${Date.now()}`,
+        name: item.originalName || `申请附件${index + 1}`,
+        url: item.url,
+        ossId: item.ossId,
+        status: 'success',
+        fileSize: item.fileSize || 0, // 使用OSS表中的fileSize字段
+        uploadTime: item.createTime,
+        response: {
+          code: 200,
+          data: {
+            url: item.url,
+            ossId: item.ossId
+          }
+        }
+      }));
+
+      applicationDetailAttachmentList.value = attachmentFiles;
+      console.log('申请详情附件数据处理完成,共', attachmentFiles.length, '个附件');
+    } else {
+      console.warn('获取申请附件信息失败:', response);
+      applicationDetailAttachmentList.value = [];
+    }
+  } catch (error) {
+    console.error('处理申请详情附件数据失败:', error);
+    applicationDetailAttachmentList.value = [];
+  }
+}
+
+/** 处理审批记录详情的附件数据 */
+async function handleApprovalRecordDetailAttachmentData(attachmentIds: string | null | undefined) {
+  try {
+    if (!attachmentIds || !attachmentIds.toString().trim()) {
+      console.log('审批记录详情没有附件数据,清空附件列表');
+      approvalRecordDetailAttachmentList.value = [];
+      return;
+    }
+
+    console.log('开始处理审批记录详情附件数据:', attachmentIds);
+
+    const ossIdsArray = attachmentIds
+      .toString()
+      .split(',')
+      .map((id) => id.trim())
+      .filter((id) => id);
+
+    if (ossIdsArray.length === 0) {
+      approvalRecordDetailAttachmentList.value = [];
+      return;
+    }
+
+    const response = await request({
+      url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
+      method: 'get'
+    });
+
+    if (response.code === 200 && response.data) {
+      console.log('获取到的审批记录详情附件信息:', response.data);
+
+      const attachmentFiles = response.data.map((item: any, index: number) => ({
+        uid: `approval_record_detail_${index}_${Date.now()}`,
+        name: item.originalName || `审批记录附件${index + 1}`,
+        url: item.url,
+        ossId: item.ossId,
+        status: 'success',
+        fileSize: item.fileSize || 0, // 使用OSS表中的fileSize字段
+        uploadTime: item.createTime,
+        response: {
+          code: 200,
+          data: {
+            url: item.url,
+            ossId: item.ossId
+          }
+        }
+      }));
+
+      approvalRecordDetailAttachmentList.value = attachmentFiles;
+      console.log('审批记录详情附件数据处理完成,共', attachmentFiles.length, '个附件');
+    } else {
+      console.warn('获取审批记录详情附件信息失败:', response);
+      approvalRecordDetailAttachmentList.value = [];
+    }
+  } catch (error) {
+    console.error('处理审批记录详情附件数据失败:', error);
+    approvalRecordDetailAttachmentList.value = [];
+  }
+}
+
 // 初始化
 onMounted(() => {
   getApplicationList();
   getApprovalRecordList(); // 添加这一行,同时加载审批记录列表
+  // 初始化教师列表
+  loadTeacherListForDisplay();
 });
 </script>
 
@@ -1124,4 +1546,62 @@ onMounted(() => {
   padding: 20px;
   color: #909399;
 }
+
+/* 审批历史项样式 */
+.approval-history-item {
+  padding: 10px 0;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.approval-history-item:last-child {
+  border-bottom: none;
+}
+
+.approval-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 5px;
+}
+
+.approver-name {
+  font-weight: bold;
+  margin-right: 10px;
+}
+
+.approval-opinion {
+  font-size: 14px;
+  color: #606266;
+  line-height: 1.6;
+}
+
+/* 拒绝原因样式 */
+.reject-reason {
+  .reject-info {
+    .reject-title {
+      font-weight: bold;
+      font-size: 16px;
+    }
+
+    .reject-content {
+      margin-top: 10px;
+      line-height: 1.6;
+    }
+
+    .reject-action {
+      margin-top: 15px;
+      text-align: right;
+    }
+  }
+}
+
+/* 附件区域样式 */
+.attachment-section {
+  .section-title {
+    font-size: 14px;
+    font-weight: bold;
+    color: #409eff;
+    display: flex;
+    align-items: center;
+  }
+}
 </style>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů