“修复培训审批v0923”

This commit is contained in:
weizhong committed 2025-09-23 19:38:41 +08:00
1 parent 0876b60056
commit 06731fd6e0
4 files changed
+233 -178

No files matched your search

@@ -127,4 +127,14 @@ public class RsTrainingRecord extends BaseEntity {
private String courseName;
private Integer duration;
private String location;
// 关联字段 - 从申请表关联查询(不存储在记录表中)
@TableField(exist = false)
private String organizerUnit; // 主办单位
@TableField(exist = false)
private String trainingType; // 培训类型
@TableField(exist = false)
private String companions; // 同行人员
}
@@ -37,6 +37,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="duration" column="duration"/>
<result property="location" column="location"/>
<result property="deptId" column="dept_id"/>
<!-- 从申请表关联查询的字段 -->
<result property="organizerUnit" column="organizer_unit"/>
<result property="trainingType" column="training_type"/>
<result property="companions" column="companions"/>
</resultMap>
<sql id="selectRsTrainingRecordVo">
@@ -44,8 +49,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
r.completion_status, r.completion_time, r.score, r.certificate_path, r.certificate_number, r.attachment_ids,
r.feedback, r.tenant_id, r.create_dept, r.create_by, r.create_time, r.update_by, r.update_time,
r.department, r.position, r.reason, r.status, r.apply_time, r.current_approver, r.start_time, r.end_time,
r.course_name, r.duration, r.location, r.dept_id
r.course_name, r.duration, r.location, r.dept_id,
a.course_name as organizer_unit, a.training_type, a.companions
from rs_training_record r
left join rs_training_application a on r.application_id = a.application_id and a.deleted = '0'
</sql>
<select id="selectRsTrainingRecordList" parameterType="RsTrainingRecord" resultMap="RsTrainingRecordResult">
+6
View File
@@ -163,6 +163,12 @@ export interface RecordVO {
createBy: string;
updateBy: string;
remark: string;
// 关联字段 - 从申请表获取
organizerUnit?: string; // 主办单位
trainingType?: string; // 培训类型
companions?: string; // 同行人员
startTime?: string; // 开始时间
endTime?: string; // 结束时间
}
// 培训记录表单类型
+209 -177
View File
@@ -213,20 +213,44 @@
@row-dblclick="handleViewRecord"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="课程名称" align="center" prop="courseName" min-width="150" :show-overflow-tooltip="true" />
<el-table-column label="培训地点" align="center" prop="location" min-width="110" :show-overflow-tooltip="true" />
<el-table-column label="部门" align="center" prop="department" min-width="110" :show-overflow-tooltip="true" />
<el-table-column label="完成状态" align="center" prop="completionStatus" width="100">
<!-- 主办单位:从申请表course_name字段关联获取 -->
<el-table-column label="主办单位" align="center" prop="organizerUnit" min-width="150" :show-overflow-tooltip="true">
<template #default="scope">
<el-tag :type="scope.row.completionStatus === '0' ? 'warning' : scope.row.completionStatus === '1' ? 'success' : 'info'">
{{ scope.row.completionStatus === '0' ? '未完成' : scope.row.completionStatus === '1' ? '已完成' : '未知' }}
</el-tag>
<span>{{ scope.row.organizerUnit || '暂无' }}</span>
</template>
</el-table-column>
<el-table-column label="学时" align="center" prop="duration" width="80" />
<el-table-column label="完成时间" align="center" prop="completionTime" width="160">
<!-- 培训类型:从申请表training_type字段关联获取 -->
<el-table-column label="培训类型" align="center" prop="trainingType" min-width="110" :show-overflow-tooltip="true">
<template #default="scope">
<span>{{ parseTime(scope.row.completionTime) || '未完成' }}</span>
<span>{{ scope.row.trainingType || '暂无' }}</span>
</template>
</el-table-column>
<!-- 开始时间:从记录表start_time字段获取 -->
<el-table-column label="开始时间" align="center" prop="startTime" width="160">
<template #default="scope">
<span>{{ parseTime(scope.row.startTime) || '暂无' }}</span>
</template>
</el-table-column>
<!-- 结束时间:从记录表end_time字段获取 -->
<el-table-column label="结束时间" align="center" prop="endTime" width="160">
<template #default="scope">
<span>{{ parseTime(scope.row.endTime) || '暂无' }}</span>
</template>
</el-table-column>
<!-- 同行人员:从申请表companions字段关联获取,格式化为教师姓名 -->
<el-table-column label="同行人员" align="center" prop="companions" min-width="120" :show-overflow-tooltip="true">
<template #default="scope">
<span v-if="scope.row.companions && scope.row.companions !== '[]'">
{{ formatCompanions(scope.row.companions) }}
</span>
<span v-else>无</span>
</template>
</el-table-column>
<el-table-column label="完成状态" align="center" prop="completionStatus" width="100">
<template #default="scope">
<el-tag :type="getCompletionStatusType(scope.row.completionStatus)">
{{ getCompletionStatusLabel(scope.row.completionStatus) }}
</el-tag>
</template>
</el-table-column>
@@ -742,7 +766,6 @@
</el-form-item>
<el-form-item label="完成状态" prop="completionStatus">
<el-radio-group v-model="recordForm.completionStatus">
<el-radio label="0">进行中</el-radio>
<el-radio label="1">已完成</el-radio>
<el-radio label="2">未完成</el-radio>
</el-radio-group>
@@ -860,79 +883,58 @@
</el-dialog>
<!-- 培训记录详情对话框 -->
<el-dialog title="培训记录详情" v-model="recordDetailDialogVisible" width="700px" append-to-body>
<el-dialog title="培训记录详情" v-model="recordDetailDialogVisible" width="800px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="课程名称">{{ recordDetail.courseName }}</el-descriptions-item>
<!-- 学员基本信息 -->
<el-descriptions-item label="学员姓名">{{ recordDetail.userName }}</el-descriptions-item>
<el-descriptions-item label="所在部门(系部)">{{ recordDetail.department || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="现职称">{{ recordDetail.currentTitle || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="现岗位">{{ recordDetail.currentPosition || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="参加工作时间">{{ parseTime(recordDetail.workStartTime) || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ recordDetail.phone || '暂无' }}</el-descriptions-item>
<!-- 培训情况信息 -->
<el-descriptions-item label="主办单位">{{ recordDetail.organizerUnit || recordDetail.courseName || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="培训地点">{{ recordDetail.location || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ parseTime(recordDetail.startTime) || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ parseTime(recordDetail.endTime) || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="培训类型">{{ recordDetail.trainingType || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="培训费用">{{ recordDetail.trainingFee ? `${recordDetail.trainingFee}元` : '暂无' }}</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ parseTime(recordDetail.applyTime) || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="完成状态">
<el-tag :type="recordDetail.completionStatus === '0' ? 'warning' : recordDetail.completionStatus === '1' ? 'success' : 'info'">
{{ recordDetail.completionStatus === '0' ? '进行中' : recordDetail.completionStatus === '1' ? '已完成' : '未完成' }}
<el-tag :type="getCompletionStatusType(recordDetail.completionStatus)">
{{ getCompletionStatusLabel(recordDetail.completionStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="学时">{{ recordDetail.duration || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="完成时间">{{ parseTime(recordDetail.completionTime) || '未完成' }}</el-descriptions-item>
<el-descriptions-item label="证书" :span="2">
<div v-if="getCertificatePath(recordDetail)" class="certificate-preview">
<!-- 证书预览区域 -->
<div class="certificate-container">
<!-- 统一显示为图片预览,支持OSS ID和传统路径 -->
<div class="image-preview">
<el-image
v-if="certificatePreviewUrl"
:src="certificatePreviewUrl"
:preview-src-list="[certificatePreviewUrl]"
fit="contain"
style="width: 300px; height: 200px; border: 1px solid #dcdfe6; border-radius: 4px"
preview-teleported
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
<div>加载失败</div>
</div>
</template>
</el-image>
<div
v-else
class="loading-preview"
style="
width: 300px;
height: 200px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
"
>
<el-icon class="is-loading"><Loading /></el-icon>
<span style="margin-left: 8px">加载中...</span>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="certificate-actions" style="margin-top: 10px">
<el-button type="primary" size="small" @click="viewCertificateDetail(recordDetail)">
<el-icon><View /></el-icon>
查看原图
</el-button>
<el-button type="success" size="small" @click="downloadCertificateFile(recordDetail)">
<el-icon><Download /></el-icon>
下载证书
</el-button>
</div>
</div>
<div v-else class="no-certificate">
<el-empty description="暂无证书" :image-size="60" />
</div>
</el-descriptions-item>
<el-descriptions-item label="证书编号">{{ recordDetail.certificateNumber || '无' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ parseTime(recordDetail.createTime) }}</el-descriptions-item>
<el-descriptions-item label="培训反馈" :span="2">
<div class="detail-text">{{ recordDetail.feedback || '暂无反馈' }}</div>
</el-descriptions-item>
<el-descriptions-item label="同行人" :span="2">{{ getCompanionNames(recordDetail.companions) || '无' }}</el-descriptions-item>
<el-descriptions-item label="培训目的" :span="2">{{ recordDetail.trainingPurpose || recordDetail.reason || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ recordDetail.remark || '暂无备注' }}</el-descriptions-item>
</el-descriptions>
<!-- 申请附件显示区域 -->
<div v-if="recordDetailAttachmentList.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="recordDetailAttachmentList" 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="recordDetailDialogVisible = false">关 闭</el-button>
@@ -1270,8 +1272,7 @@ const recordRules = reactive({
// 培训记录详情
const recordDetailDialogVisible = ref(false);
const certificatePreviewUrl = ref('');
const recordDetail = reactive<RecordVO>({
const recordDetail = reactive<any>({
recordId: undefined,
courseId: undefined,
courseName: '',
@@ -1285,7 +1286,22 @@ const recordDetail = reactive<RecordVO>({
updateTime: '',
createBy: '',
updateBy: '',
remark: ''
remark: '',
// 从培训申请获取的详细信息
department: '',
currentTitle: '',
currentPosition: '',
workStartTime: '',
phone: '',
organizerUnit: '',
trainingType: '',
trainingFee: '',
companions: '',
trainingPurpose: '',
location: '',
startTime: '',
endTime: '',
applyTime: ''
});
// 培训记录多选框选中数据
@@ -1323,6 +1339,7 @@ const applicationDialogLoading = ref(false);
const applicationFormRef = ref<any>(null);
const applicationAttachmentFileList = ref<any[]>([]); // 申请培训附件文件列表
const applicationDetailAttachmentList = ref<any[]>([]); // 申请详情附件文件列表
const recordDetailAttachmentList = ref<any[]>([]); // 培训记录详情附件文件列表
const applicationForm = reactive<{
applicationId?: number | string;
// 情况基本信息
@@ -1620,6 +1637,24 @@ function getApplicationList() {
});
}
/** 获取完成状态的标签类型 */
const getCompletionStatusType = (status: string) => {
// 培训记录的完成状态逻辑
if (status === '1') return 'success'; // 已完成 - 绿色
if (status === '2') return 'danger'; // 未完成 - 红色
// 对于空状态或其他状态,由于培训记录都是基于已通过审批的申请生成的,默认显示为已通过
return 'success'; // 已通过 - 绿色
};
/** 获取完成状态的标签文本 */
const getCompletionStatusLabel = (status: string) => {
// 培训记录的完成状态逻辑
if (status === '1') return '已完成';
if (status === '2') return '未完成';
// 对于空状态或其他状态,由于培训记录都是基于已通过审批的申请生成的,默认显示为已通过
return '已通过';
};
/** 获取教师列表 */
function getTeacherList() {
listClassTeacherCounselorRel({})
@@ -1712,6 +1747,8 @@ async function handleApplicationDetailAttachmentData(attachmentIds: string | nul
}
}
/** 新增培训申请 */
async function handleAddApplication() {
resetApplicationForm();
@@ -2029,6 +2066,11 @@ function getCompanionNames(companions: (string | number)[] | string | null | und
}).join('、');
}
/** 格式化同行人员显示(表格列使用) */
function formatCompanions(companions: string | undefined): string {
return getCompanionNames(companions);
}
/** 查看申请详情 */
async function handleViewApplicant(row: any) {
// 如果行数据中没有完整的课程名称,尝试从课程列表获取
@@ -2372,8 +2414,16 @@ function handleRecordSelectionChange(selection: RecordVO[]) {
function handleTrainingSubTabClick() {
if (trainingSubTab.value === 'application') {
getApplicationList();
// 确保教师列表已加载(用于同行人显示)
if (teacherList.value.length === 0) {
loadTeacherListForDisplay();
}
} else if (trainingSubTab.value === 'record') {
getRecordList();
// 确保教师列表已加载(用于同行人显示)
if (teacherList.value.length === 0) {
loadTeacherListForDisplay();
}
} else {
getTrainingList();
}
@@ -2430,6 +2480,11 @@ function getRecordList() {
recordList.value = response.rows;
recordTotal.value = response.total;
recordLoading.value = false;
// 确保教师列表已加载(用于同行人显示)
if (teacherList.value.length === 0) {
loadTeacherListForDisplay();
}
})
.catch(() => {
recordLoading.value = false;
@@ -2453,7 +2508,7 @@ function resetRecordForm() {
recordForm.recordId = undefined;
recordForm.courseId = '';
recordForm.userId = '';
recordForm.completionStatus = '0';
recordForm.completionStatus = '1';
recordForm.score = '';
recordForm.completionTime = '';
recordForm.certificatePath = '';
@@ -2670,31 +2725,78 @@ async function handleUpdateRecord(row: RecordVO) {
/** 查看培训记录详情 */
async function handleViewRecord(row: RecordVO) {
// 如果行数据中没有完整的课程名称,尝试从课程选项获取
if (!row.courseName && row.courseId) {
const course = courseOptions.value.find((c) => c.courseId === row.courseId);
if (course) {
row.courseName = course.title;
try {
// 确保教师列表已加载(用于显示同行人姓名)
if (teacherList.value.length === 0) {
await loadTeacherListForDisplay();
}
}
// 复制记录详情
Object.assign(recordDetail, row);
// 首先复制记录的基本信息
Object.assign(recordDetail, row);
// 异步获取证书预览URL
const certificatePath = getCertificatePath(row);
if (certificatePath) {
try {
certificatePreviewUrl.value = await getSecureCertificatePreviewUrl(certificatePath);
} catch (error) {
console.error('获取证书预览URL失败:', error);
certificatePreviewUrl.value = '';
// 如果行数据中没有完整的课程名称,尝试从课程选项获取
if (!row.courseName && row.courseId) {
const course = courseOptions.value.find((c) => c.courseId === row.courseId);
if (course) {
recordDetail.courseName = course.title;
}
}
} else {
certificatePreviewUrl.value = '';
}
recordDetailDialogVisible.value = true;
// 如果有申请ID,获取完整的申请详情来补充缺失的字段
if (row.applicationId) {
try {
const applicationResponse = await getTrainingApplication(row.applicationId);
const applicationData = applicationResponse.data;
// 合并申请数据中的详细信息到记录详情
Object.assign(recordDetail, {
// 保留记录的核心信息
recordId: row.recordId,
completionStatus: row.completionStatus,
completionTime: row.completionTime,
certificatePath: row.certificatePath,
certificateNumber: row.certificateNumber,
feedback: row.feedback,
// 从申请数据中获取详细信息
department: applicationData.department || row.department,
currentTitle: applicationData.currentTitle,
currentPosition: applicationData.currentPosition,
workStartTime: applicationData.workStartTime,
phone: applicationData.phone,
organizerUnit: applicationData.organizerUnit,
trainingType: applicationData.trainingType,
trainingFee: applicationData.trainingFee,
companions: applicationData.companions,
trainingPurpose: applicationData.trainingPurpose || applicationData.reason,
location: applicationData.location || row.location,
startTime: applicationData.startTime,
endTime: applicationData.endTime,
applyTime: applicationData.applyTime,
// 其他基本信息
courseName: applicationData.courseName || recordDetail.courseName,
userName: applicationData.userName || row.userName,
duration: applicationData.duration || row.duration,
remark: row.remark
});
// 加载申请附件数据
if (applicationData.attachmentIds) {
await handleApplicationDetailAttachmentData(applicationData.attachmentIds);
recordDetailAttachmentList.value = [...applicationDetailAttachmentList.value];
}
} catch (error) {
console.warn('获取申请详情失败,使用记录基本信息:', error);
// 如果获取申请详情失败,继续使用记录的基本信息
}
}
recordDetailDialogVisible.value = true;
} catch (error) {
console.error('查看培训记录详情失败:', error);
proxy?.$modal.msgError('获取培训记录详情失败,请重试');
}
}
/** 删除培训记录 */
@@ -3234,82 +3336,6 @@ function getCertificateFileName(filePath: string): string {
return fileName;
}
/** 查看证书详情 */
async function viewCertificateDetail(row: any) {
const certificatePath = getCertificatePath(row);
if (!certificatePath) {
proxy?.$modal.msgWarning('该培训记录暂无证书');
return;
}
try {
const previewUrl = await getSecureCertificatePreviewUrl(certificatePath);
if (previewUrl) {
window.open(previewUrl, '_blank');
} else {
proxy?.$modal.msgError('无法获取证书预览链接');
}
} catch (error) {
console.error('获取证书预览链接失败:', error);
proxy?.$modal.msgError('获取证书预览链接失败');
}
}
/** 下载证书文件 */
async function downloadCertificateFile(row: any) {
const certificatePath = getCertificatePath(row);
if (!certificatePath) {
proxy?.$modal.msgWarning('该培训记录暂无证书');
return;
}
try {
// 如果是OSS ID,使用专门的下载方法
if (/^\d+$/.test(certificatePath.trim())) {
// 使用request下载,带认证信息
const loading = ElLoading.service({
text: '正在下载证书...',
background: 'rgba(0, 0, 0, 0.7)'
});
try {
const response = await request({
url: `/resource/oss/download/${certificatePath.trim()}`,
method: 'get',
responseType: 'blob',
timeout: 300000
});
if (response) {
const blob = response instanceof Blob ? response : response.data;
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = getCertificateFileName(certificatePath) || `certificate_${certificatePath}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}
} finally {
loading?.close();
}
} else {
// 普通文件路径的处理
const downloadUrl = getCertificatePreviewUrl(certificatePath);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = getCertificateFileName(certificatePath);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
} catch (error) {
console.error('下载证书失败:', error);
proxy?.$modal.msgError('下载证书失败');
}
}
/** 下载证书 */
function downloadCertificate(row: RecordVO) {
if (!row.certificatePath) {
@@ -3635,6 +3661,10 @@ onActivated(() => {
// 同时加载课程选项,确保下拉选项可用
loadCourseOptions();
// 确保教师列表已加载(用于同行人显示)
if (teacherList.value.length === 0) {
loadTeacherListForDisplay();
}
});
onMounted(() => {
@@ -3646,6 +3676,8 @@ onMounted(() => {
getRecordList();
loadCourseOptions();
getDepartmentList();
// 加载教师列表(用于同行人显示)
loadTeacherListForDisplay();
});
// ===== 申请培训附件上传相关方法 =====