Files
hzdyjs/web/src/views/xg/xsjcxx/xsqj/xjdialog.vue
T
2025-08-06 01:32:11 +08:00

1560 lines
45 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="p-2">
<div class="dialog-content">
<!-- 顶部按钮区域(流程相关,保留) -->
<div class="top-button-section">
<div class="left-buttons">
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="cancelForm.id"
:status="leaveInfo.status"
:pageType="routeParams.type"
/>
</div>
</div>
<!-- 上半部分:请假信息展示(只读) -->
<div class="student-info-section">
<div class="info-title">请假信息</div>
<div class="info-content">
<div class="info-row">
<div class="info-item">
<span class="info-label">学生姓名:</span>
<span class="info-value">{{ leaveInfo.studentName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">学号:</span>
<span class="info-value">{{ leaveInfo.studentNo || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">班级名称:</span>
<span class="info-value">{{ leaveInfo.className || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">专业名称:</span>
<span class="info-value">{{ leaveInfo.majorName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">学院名称:</span>
<span class="info-value">{{ leaveInfo.collegeName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">请假类型:</span>
<span class="info-value">{{ leaveInfo.leaveTypeName || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">请假时间:</span>
<span class="info-value">{{ leaveInfo.startTime }} ~ {{ leaveInfo.endTime }}</span>
</div>
<div class="info-item">
<span class="info-label">请假时长:</span>
<span class="info-value">{{ leaveInfo.leaveDuration }} 天</span>
</div>
<div class="info-item">
<span class="info-label">请假原因:</span>
<el-tooltip
:content="leaveInfo.leaveReason || '-'"
placement="top"
:show-after="500"
:hide-after="0"
popper-class="leave-reason-tooltip"
>
<span class="info-value text-ellipsis">{{ leaveInfo.leaveReason || '-' }}</span>
</el-tooltip>
</div>
</div>
</div>
<!-- 请假附件展示(只读,6列,下载按钮) -->
<div class="attachment-upload-container readonly-attachment">
<div class="readonly-upload-list">
<div v-for="file in leaveAttachmentList" :key="file.uid" class="upload-file-card readonly">
<div class="upload-file-thumb">
<image-preview v-if="isImageFile(file) && file.url" :src="getFileOssId(file)" class="upload-thumb-image" :isOssId="true" />
<div v-else class="upload-file-placeholder">
<el-icon><Document /></el-icon>
<span class="file-ext">{{ getFileExt(file.name) }}</span>
</div>
</div>
<div class="upload-file-name-row">
<span class="upload-file-name" :title="file.name">
{{ getShortFileName(file.name) }}
</span>
<el-icon class="download-icon" @click.stop="handleDownloadAttachment(file)" title="下载">
<Download />
</el-icon>
</div>
</div>
</div>
</div>
</div>
<!-- 第二段:销假信息 -->
<el-card class="content-section leave-info-card" shadow="hover">
<template #header>
<div class="card-header">
<span class="section-title">销假信息</span>
</div>
</template>
<div class="leave-info-content">
<el-form ref="cancelFormRef" :model="cancelForm" :rules="cancelRules" label-width="120px" class="section-form compact-form">
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="销假时间" prop="cancelTime" required>
<el-date-picker
v-model="cancelForm.cancelTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
placeholder="请选择销假时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="销假地点" prop="cancelLocation">
<el-input v-model="cancelForm.cancelLocation" placeholder="请输入销假地点" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="销假备注" prop="cancelRemark">
<el-input v-model="cancelForm.cancelRemark" type="textarea" :rows="2" placeholder="请输入销假备注" />
</el-form-item>
<el-form-item label="销假附件">
<div class="attachment-upload-container">
<el-upload
:file-list="cancelAttachmentList"
class="upload-demo"
drag
list-type="picture-card"
:on-preview="handleFilePreview"
:action="uploadAction"
:headers="uploadHeaders"
:data="uploadData"
:auto-upload="true"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleRemoveAttachmentConfirm"
:on-change="handleFileChange"
multiple
:limit="15"
:show-file-list="true"
accept="image/*,video/*,.pdf,.doc,.docx,.txt"
>
<template #tip>
<div class="el-upload__tip">
支持图片、视频、PDF、Word文档等格式,单个文件大小不超过10MB,最多上传15个文件。
<span class="el-upload__tip" v-if="!cancelForm.id" style="color: #ff0000"
>(新增时文件会立即上传,保存销假申请后自动关联)</span
>
<span class="el-upload__tip" v-else style="color: #ff0000">(编辑时文件会立即上传并关联到当前销假申请)</span>
</div>
</template>
<!-- 调整拖拽上传图标与文字在同一行、居中对齐 -->
<div class="el-upload-dragger1">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</div>
<!-- 自定义文件卡片缩略图(含文件名) -->
<template #file="{ file }">
<div class="upload-file-card">
<!-- 缩略图区域 -->
<div class="upload-file-thumb">
<image-preview v-if="isImageFile(file) && file.url" :src="getFileOssId(file)" class="upload-thumb-image" :isOssId="true" />
<div v-else class="upload-file-placeholder">
<el-icon><Document /></el-icon>
<span class="file-ext">{{ getFileExt(file.name) }}</span>
</div>
<!-- loading 遮罩只在上传中显示 -->
<div v-if="file.status === 'uploading'" class="upload-file-loading">
<el-icon class="is-loading"><Loading /></el-icon>
<span class="loading-text">上传中...</span>
</div>
</div>
<!-- 文件名称 -->
<div class="upload-file-name" :title="file.name">
{{ getShortFileName(file.name) }}
</div>
<!-- 操作按钮区域 - 固定在文件名下方 -->
<div class="upload-file-card-actions" v-if="file.status && file.status !== 'ready'">
<el-button-group size="small">
<el-button type="success" @click.stop="handleDownloadAttachment(file)" title="下载" :disabled="file.status !== 'success'">
<el-icon><Download /></el-icon>
</el-button>
<el-button type="danger" @click.stop="handleRemoveAttachmentConfirm(file)" title="删除">
<el-icon><Delete /></el-icon>
</el-button>
</el-button-group>
</div>
</div>
</template>
</el-upload>
</div>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- 提交组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<!-- 审批记录 -->
<approvalRecord ref="approvalRecordRef" />
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" :before-close="handleClose" width="500">
<el-select v-model="flowCode" placeholder="Select" style="width: 240px">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="submitFlow()"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup name="StudentLeaveCancelDialog" lang="ts">
import { ref, computed, onMounted, reactive, toRefs, nextTick, getCurrentInstance, watch, type ComponentInternalInstance } from 'vue';
import ElMessage from 'element-plus/es/components/message/index';
import ElMessageBox from 'element-plus/es/components/message-box/index';
import ElLoading from 'element-plus/es/components/loading/index';
import { UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
import { getToken } from '@/utils/auth';
import ImagePreview from '@/components/ImagePreview/index.vue';
import request from '@/utils/request';
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import { AxiosResponse } from 'axios';
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
import { getStudentLeave, updateStudentLeave } from '@/api/workflow/studentLeave/studentLeave';
import { getStudentMsg } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
import { startWorkFlow } from '@/api/workflow/task';
import { StudentLeaveForm, StudentLeaveVO } from '@/api/workflow/studentLeave/studentLeave/types';
import { StudentMsgVO } from '@/api/xg/xsjcxx/studentMsg/studentMsg/types';
import { getClassTeacherId } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
import {
getStudentCancelLeave,
updateStudentCancelLeave,
getCancelLeaveIdByLeaveId,
getLeaveIdByCancelLeaveId,
addStudentCancelLeave
} from '@/api/workflow/studentCancelLeave/studentCancelLeave';
// 类型定义
interface DialogOption {
visible: boolean;
title: string;
}
interface ElFormInstance {
validate: (callback?: (valid: boolean) => void) => Promise<boolean>;
resetFields: () => void;
}
interface PageData<T, Q> {
form: T;
queryParams: Q;
rules: Record<string, any>;
}
interface BaseEntity {
id?: string | number;
createBy?: number;
createTime?: string;
updateBy?: number;
updateTime?: string;
remark?: string;
}
// 扩展文件类型定义
interface UploadFile {
uid: string;
name: string;
url?: string;
ossId?: string;
status?: string;
response?: any;
raw?: File;
}
// 定义emits
interface Emits {
(e: 'submit'): void;
(e: 'cancel'): void;
}
const emit = defineEmits<Emits>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 响应式数据 - 请假信息(只读展示)
const leaveInfo = reactive<any>({
id: undefined,
studentId: '',
studentName: '',
studentNo: '',
classId: '',
className: '',
majorId: '',
majorName: '',
collegeId: '',
collegeName: '',
leaveTypeCode: '',
leaveTypeName: '',
startTime: '',
endTime: '',
leaveDuration: '',
isLeaveSchool: '0',
needCancel: '1',
emergencyContact: '',
emergencyPhone: '',
leaveReason: '',
leaveLocation: '',
attachmentOssIds: '',
approvalStatus: 'draft',
approvalOpinion: '',
approvalTime: '',
approvalBy: '',
approvalByName: '',
cancelStatus: '0',
cancelTime: '',
cancelLocation: '',
status: 'draft'
});
// 销假表单数据
const cancelForm = reactive<any>({
id: undefined,
cancelTime: '',
cancelLocation: '',
cancelRemark: '',
cancelPhotoOssIds: ''
// cancelStatus: '1' // 已销假
});
// 销假表单验证规则
const cancelRules = reactive({
cancelTime: [{ required: true, message: '请选择销假时间', trigger: 'change' }],
cancelLocation: [{ required: true, message: '请输入销假地点', trigger: 'blur' }]
});
const buttonLoading = ref(false);
const cancelFormRef = ref();
const leaveAttachmentList = ref<any[]>([]); // 请假附件列表(只读)
const cancelAttachmentList = ref<any[]>([]); // 销假附件列表
const loading = ref(true);
const flowCode = ref<string>('');
const dialogVisible = reactive<DialogOption>({
visible: false,
title: '流程定义'
});
//提交组件
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
//审批记录组件
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
//按钮组件
const approvalButtonRef = ref<InstanceType<typeof ApprovalButton>>();
const submitFormData = ref<StartProcessBo>({
businessId: '',
flowCode: '',
variables: {}
});
const taskVariables = ref<Record<string, any>>({});
//路由参数
const routeParams = ref<Record<string, any>>({});
// 流程代码选项
const flowCodeOptions = [
{
value: 'hzdyjs-xj',
label: '学生销假流程-杭州第一技师'
}
];
// 上传相关配置
const uploadAction = computed(() => {
return import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
});
const uploadHeaders = computed(() => {
return {
'Authorization': `Bearer ${getToken() || ''}`,
'clientid': import.meta.env.VITE_APP_CLIENT_ID || 'e5cd7e4891bf95d1d19206ce24a7b32e'
};
});
const uploadData = computed(() => {
return {
bizType: 'student_leave_cancel_attachment'
};
});
// 文件类型判断
const isImageFile = (file: any) => {
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
return imageTypes.includes(file.raw?.type) || (file.url && /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name));
};
// 获取文件OSS ID的辅助函数
const getFileOssId = (file: any) => {
return file.ossId || '';
};
// 获取文件扩展名
const getFileExt = (fileName: string) => {
const ext = fileName.split('.').pop()?.toUpperCase();
return ext || 'FILE';
};
// 获取缩写的文件名(保留后缀)
const getShortFileName = (fileName: string) => {
if (!fileName) return '';
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex === -1) {
// 没有后缀的文件名
return fileName.length > 12 ? fileName.substring(0, 12) + '...' : fileName;
}
const name = fileName.substring(0, lastDotIndex);
const ext = fileName.substring(lastDotIndex); // 包含点号的后缀
if (name.length <= 8) {
return fileName; // 文件名较短,直接返回
}
// 文件名较长,缩写中间部分,保留后缀
return name.substring(0, 8) + '...' + ext;
};
// 文件预览
const handleFilePreview = (file: any) => {
if (isImageFile(file)) {
// 图片预览
window.open(file.url);
} else {
// 其他文件下载
handleDownloadAttachment(file);
}
};
// 文件上传前检查
const handleBeforeUpload = (file: any) => {
const isValidType = /\.(jpg|jpeg|png|gif|bmp|webp|pdf|doc|docx|txt|mp4|avi|mov)$/i.test(file.name);
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isValidType) {
ElMessage.error('上传文件格式不正确!');
return false;
}
if (!isLt10M) {
ElMessage.error('上传文件大小不能超过 10MB!');
return false;
}
return true;
};
// 文件上传成功
const handleUploadSuccess = (response: any, file: any) => {
if (response.code === 200) {
file.url = response.data.url;
file.ossId = response.data.ossId;
ElMessage.success('文件上传成功');
} else {
ElMessage.error(response.msg || '文件上传失败');
}
};
// 文件上传失败
const handleUploadError = (error: any, file: any) => {
console.error('文件上传失败:', error);
ElMessage.error('文件上传失败');
};
// 文件变更
const handleFileChange = (file: any, fileList: any) => {
console.log('文件变更:', file, fileList);
cancelAttachmentList.value = fileList;
};
// 删除附件确认
const handleRemoveAttachmentConfirm = (file: any) => {
ElMessageBox.confirm('确定要删除这个文件吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
handleRemoveAttachment(file);
})
.catch(() => {
// 取消删除
});
};
// 删除附件
const handleRemoveAttachment = (file: any) => {
const index = cancelAttachmentList.value.findIndex((item: any) => item.uid === file.uid);
if (index > -1) {
cancelAttachmentList.value.splice(index, 1);
ElMessage.success('文件删除成功');
}
};
// 下载附件
const handleDownloadAttachment = async (file: any) => {
let downloadUrl = '';
let fileName = file.name || 'attachment';
// 如果文件已经上传成功,使用服务器返回的路径
if (file.response && file.response.data && file.response.data.filePath) {
downloadUrl = file.response.data.filePath;
fileName = file.response.data.fileName || fileName;
}
// 如果是已有的附件,使用文件的url
else if (file.ossId) {
downloadUrl = file.ossId;
}
// 如果是本地文件,直接使用raw文件
else if (file.raw) {
const link = document.createElement('a');
link.href = URL.createObjectURL(file.raw);
link.download = fileName;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
console.log('下载本地文件:', fileName);
return;
}
if (!downloadUrl) {
ElMessage.error('无法获取文件下载地址');
return;
}
// 对于服务器文件,使用fetch获取内容强制下载
try {
const loading = ElLoading.service({
text: '正在下载文件...',
target: document.body
});
const response = await request({
url: '/resource/oss/download/' + downloadUrl,
method: 'get',
responseType: 'blob',
timeout: 300000 // 300秒超时
});
// 验证响应数据
if (!response) {
throw new Error('响应数据为空');
}
const url = window.URL.createObjectURL(response.data as Blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
loading?.close();
console.log('下载服务器文件成功:', fileName);
} catch (error) {
console.error('下载文件失败:', error);
ElMessage.error('下载文件失败,请稍后重试');
}
};
// 工作流相关方法
const handleClose = () => {
dialogVisible.visible = false;
flowCode.value = '';
buttonLoading.value = false;
};
const submitFlow = async () => {
handleStartWorkFlow(cancelForm);
dialogVisible.visible = false;
};
// 启动工作流
const handleStartWorkFlow = async (data: any) => {
try {
console.log('data:', data);
// 1. 调用接口获取班主任ID
let classTeacherId: number | null = null;
try {
const classTeacherResponse = await getClassTeacherId(leaveInfo.studentId);
if (classTeacherResponse && classTeacherResponse.data) {
classTeacherId = classTeacherResponse.data;
console.log('获取到班主任ID:', classTeacherId);
} else {
console.warn('获取班主任ID失败');
ElMessage.error('获取班主任信息失败,请稍后重试');
return; // 停止后续操作
}
} catch (error) {
console.error('调用获取班主任ID接口失败:', error);
ElMessage.error('获取班主任信息失败,请稍后重试');
return; // 停止后续操作
}
submitFormData.value.flowCode = flowCode.value;
submitFormData.value.businessId = cancelForm.id;
// 流程变量
taskVariables.value = {
// 销假流程变量
// xjsj: data.cancelTime,
// xjdd: data.cancelLocation
bzrspr: classTeacherId
};
submitFormData.value.variables = taskVariables.value;
const resp = await startWorkFlow(submitFormData.value);
if (submitVerifyRef.value) {
buttonLoading.value = false;
submitVerifyRef.value.openDialog(resp.data.taskId);
}
} finally {
buttonLoading.value = false;
}
};
// 审批记录
const handleApprovalRecord = () => {
approvalRecordRef.value.init(cancelForm.id);
};
// 审批
const approvalVerifyOpen = async () => {
submitVerifyRef.value.openDialog(routeParams.value.taskId);
};
// 提交表单
const submitForm = (status: string) => {
// 验证销假时间是否已选择
if (!cancelForm.cancelTime) {
ElMessage.error('销假时间不能为空');
return;
}
// 验证销假地点是否已填写
if (!cancelForm.cancelLocation) {
ElMessage.error('销假地点不能为空');
return;
}
console.log('status:', status);
cancelFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
// 处理销假附件数据
const cancelPhotoOssIds = cancelAttachmentList.value
.filter((file: any) => file.status === 'success' && file.ossId)
.map((file: any) => file.ossId);
// 将附件ID添加到销假表单数据中,处理成逗号分隔的字符串
cancelForm.cancelPhotoOssIds = cancelPhotoOssIds.join(',');
// 更新请假记录的销假信息
const updateData = {
...leaveInfo,
cancelTime: cancelForm.cancelTime,
cancelLocation: cancelForm.cancelLocation,
cancelRemark: cancelForm.cancelRemark,
cancelPhotoOssIds: cancelForm.cancelPhotoOssIds,
// 设置销假审批状态为草稿
cancelStatus: 'draft'
// cancelStatus: '1', // 已销假
// status: 'canceled'
};
// 保存业务数据
try {
buttonLoading.value = true;
// 根据请假id查询销假信息
const cancelLeaveIdResp = await getCancelLeaveIdByLeaveId(leaveInfo.id);
let cancelLeaveId = cancelLeaveIdResp?.data;
// 如果返回为null或0,说明还没有销假记录
updateData.leaveId = leaveInfo.id;
updateData.id = cancelLeaveId;
let res;
if (cancelLeaveId) {
res = await updateStudentCancelLeave(updateData).finally(() => (buttonLoading.value = false));
} else {
// 调用后端 XgStudentCancelLeaveController 的 add 方法
res = await addStudentCancelLeave(updateData).finally(() => (buttonLoading.value = false));
// 从返回结果中获取新创建的销假记录ID
if (res && res.data && res.data.id) {
cancelLeaveId = res.data.id;
}
}
cancelForm.id = cancelLeaveId;
// 根据id 获取销假的信息 传给流程启动的函数
const cancelInfoResponse = await getStudentCancelLeave(cancelLeaveId);
const cancelInfo = cancelInfoResponse?.data;
console.log('cancelInfo:', cancelInfo);
if (status === 'draft') {
buttonLoading.value = false;
ElMessage.success('暂存成功');
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
if ((leaveInfo.cancelStatus === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
return;
}
// 说明启动过先随意穿个参数
if (flowCode.value === '' || flowCode.value === null) {
flowCode.value = 'xx';
}
await handleStartWorkFlow(cancelInfo);
}
} finally {
buttonLoading.value = false;
}
}
});
};
// 取消操作
const cancel = () => {
emit('cancel');
};
// 根据id获取学生请假信息
const getStudentLeaveInfo = async (id: string | number) => {
try {
//通过销假id获取请假id
const leaveIdResp = await getLeaveIdByCancelLeaveId(id);
let leaveId = leaveIdResp?.data;
console.log('通过销假ID获取到的请假ID:', leaveId);
id = leaveId || id;
console.log('开始获取学生请假信息,ID:', id);
const response = await getStudentLeave(id);
if (response.code === 200 && response.data) {
const leaveData = response.data;
console.log('获取到的学生请假信息:', leaveData);
// 填充请假信息表单
Object.assign(leaveInfo, leaveData);
// todo
leaveInfo.status = leaveData.cancelStatus;
console.log('leaveInfo:', leaveInfo);
//根据leaveId 获取销假信息
const cancelInfoResponse = await getCancelLeaveIdByLeaveId(leaveData.id);
const cancelId = cancelInfoResponse?.data;
console.log('cancelId:', cancelId);
Object.assign(cancelForm, { id: cancelId });
// 初始化销假表单数据
Object.assign(cancelForm, {
// id: leaveData.id,
cancelTime: leaveData.cancelTime || '',
cancelLocation: leaveData.cancelLocation || '',
cancelRemark: leaveData.cancelRemark || '',
cancelPhotoOssIds: leaveData.cancelPhotoOssIds || '',
cancelStatus: leaveData.cancelStatus || '0'
});
console.log('销假表单初始化完成:', cancelForm);
// 如果有学生ID,则获取学生信息
if (leaveData.studentId) {
await getStudentInfo(leaveData.studentId);
}
// 处理请假附件数据
await handleLeaveAttachmentData();
// 处理销假附件数据
await handleCancelAttachmentData();
} else {
ElMessage.error('获取学生请假信息失败');
}
} catch (error) {
console.error('获取学生请假信息失败:', error);
ElMessage.error('获取学生请假信息失败,请稍后重试');
}
};
// 根据学生ID获取学生信息
const getStudentInfo = async (studentId: string | number) => {
try {
console.log('开始获取学生信息,学生ID:', studentId);
const response = await getStudentMsg(studentId);
if (response.code === 200 && response.data) {
const studentInfo = response.data;
console.log('获取到的学生信息:', studentInfo);
// 填充学生基础信息
leaveInfo.studentName = studentInfo.name || '';
leaveInfo.studentNo = studentInfo.studentNo || '';
leaveInfo.className = studentInfo.className || '';
leaveInfo.majorName = studentInfo.majorName || '';
leaveInfo.collegeName = studentInfo.collegeName || '';
leaveInfo.studentId = studentInfo.id || '';
leaveInfo.classId = studentInfo.classId || '';
leaveInfo.majorId = studentInfo.majorId || '';
leaveInfo.collegeId = studentInfo.collegeId || '';
console.log('学生信息已填入表单:', {
studentName: leaveInfo.studentName,
studentNo: leaveInfo.studentNo,
className: leaveInfo.className,
majorName: leaveInfo.majorName,
collegeName: leaveInfo.collegeName,
studentId: leaveInfo.studentId
});
} else {
ElMessage.warning(response.msg || '获取学生信息失败');
}
} catch (error) {
console.error('获取学生信息失败:', error);
ElMessage.error('获取学生信息失败,请稍后重试');
}
};
// 处理请假附件数据
const handleLeaveAttachmentData = async () => {
try {
// 如果有附件ID,则获取附件信息
if (leaveInfo.attachmentOssIds && leaveInfo.attachmentOssIds.toString().trim()) {
console.log('开始处理请假附件数据:', leaveInfo.attachmentOssIds);
// 将逗号分隔的字符串转换为数组
const ossIdsArray = leaveInfo.attachmentOssIds
.split(',')
.map((id) => id.trim())
.filter((id) => id);
// 调用接口获取附件信息
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: `leave_${index}_${Date.now()}`,
name: item.originalName || `附件${index + 1}`,
url: item.url,
ossId: item.ossId,
status: 'success', // 标记为已上传成功
response: {
code: 200,
data: {
url: item.url,
ossId: item.ossId
}
}
}));
// 设置请假附件列表
leaveAttachmentList.value = attachmentFiles;
console.log('设置请假附件列表成功:', leaveAttachmentList.value);
} else {
console.warn('获取请假附件信息失败:', response.msg);
leaveAttachmentList.value = [];
}
} else {
console.log('没有请假附件数据,清空附件列表');
leaveAttachmentList.value = [];
}
} catch (error) {
console.error('处理请假附件数据失败:', error);
ElMessage.error('获取请假附件信息失败,请稍后重试');
leaveAttachmentList.value = [];
}
};
// 处理销假附件数据
const handleCancelAttachmentData = async () => {
try {
// 如果有销假附件ID,则获取附件信息
if (leaveInfo.cancelPhotoOssIds && leaveInfo.cancelPhotoOssIds.toString().trim()) {
console.log('开始处理销假附件数据:', leaveInfo.cancelPhotoOssIds);
// 将逗号分隔的字符串转换为数组
const ossIdsArray = leaveInfo.cancelPhotoOssIds
.split(',')
.map((id) => id.trim())
.filter((id) => id);
// 调用接口获取附件信息
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: `cancel_${index}_${Date.now()}`,
name: item.originalName || `附件${index + 1}`,
url: item.url,
ossId: item.ossId,
status: 'success', // 标记为已上传成功
response: {
code: 200,
data: {
url: item.url,
ossId: item.ossId
}
}
}));
// 设置销假附件列表
cancelAttachmentList.value = attachmentFiles;
console.log('设置销假附件列表成功:', cancelAttachmentList.value);
} else {
console.warn('获取销假附件信息失败:', response.msg);
cancelAttachmentList.value = [];
}
} else {
console.log('没有销假附件数据,清空附件列表');
cancelAttachmentList.value = [];
}
} catch (error) {
console.error('处理销假附件数据失败:', error);
ElMessage.error('获取销假附件信息失败,请稍后重试');
cancelAttachmentList.value = [];
}
};
// 初始化页面数据
const initPageData = () => {
// 重置销假表单数据
Object.assign(cancelForm, {
id: undefined,
cancelTime: '',
cancelLocation: '',
cancelRemark: '',
cancelPhotoOssIds: '',
cancelStatus: '0' // 已销假
});
// 清空附件列表
leaveAttachmentList.value = [];
cancelAttachmentList.value = [];
// 根据路由参数决定数据获取方式
if (routeParams.value.id) {
// 有id时,获取学生请假信息
getStudentLeaveInfo(routeParams.value.id);
} else {
ElMessage.error('缺少请假记录ID');
}
};
//提交回调
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
};
// 组件挂载时初始化数据
onMounted(() => {
nextTick(async () => {
routeParams.value = (proxy as any).$route.query;
initPageData();
});
});
</script>
<style scoped>
/********* 参考 dialog.vue 样式,适配销假表单 *********/
.dialog-content {
height: calc(100vh - 120px);
max-height: calc(100vh - 120px);
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 滚动条样式 */
.dialog-content::-webkit-scrollbar,
.leave-info-content::-webkit-scrollbar,
.attachment-upload-container::-webkit-scrollbar {
width: 6px;
}
.dialog-content::-webkit-scrollbar-track,
.leave-info-content::-webkit-scrollbar-track,
.attachment-upload-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.dialog-content::-webkit-scrollbar-thumb,
.leave-info-content::-webkit-scrollbar-thumb,
.attachment-upload-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.dialog-content::-webkit-scrollbar-thumb:hover,
.leave-info-content::-webkit-scrollbar-thumb:hover,
.attachment-upload-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.content-section {
margin-bottom: 12px;
margin-right: 20px;
}
.content-section:last-child {
margin-bottom: 0;
}
/* 请假信息卡片样式 */
.leave-info-card {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
max-height: calc(100vh - 240px);
}
.leave-info-content {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.card-header {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #e4e7ed;
background: #fafafa;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin: 0;
padding: 0;
}
.section-form {
padding: 8px 0;
}
.section-form .el-form-item {
margin-bottom: 12px;
}
/* 紧凑布局样式 */
.compact-form .el-form-item {
margin-bottom: 8px;
}
.compact-form .el-form-item__label {
padding-bottom: 4px;
}
.dialog-footer {
text-align: right;
padding-top: 20px;
}
.upload-demo {
width: 100%;
}
.el-upload__tip {
color: #909399;
font-size: 12px;
margin-top: 8px;
}
.student-info-section {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 16px; /* 增加内边距 */
margin-bottom: 16px; /* 增加下边距 */
margin-right: 20px;
width: calc(100% - 20px);
}
.info-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #e9ecef;
}
.info-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.info-row {
display: flex;
gap: 16px;
}
.info-item {
flex: 1;
display: flex;
align-items: center;
min-width: 0;
}
.info-label {
color: #606266;
font-weight: 500;
margin-right: 8px;
white-space: nowrap;
}
.info-value {
color: #303133;
font-weight: 400;
flex: 1;
word-break: break-all;
}
/* 文字溢出省略号样式 */
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
max-width: 100%;
}
/* 请假原因特殊样式 - 允许换行但限制最大高度 */
.info-item:has(.text-ellipsis) .text-ellipsis {
white-space: normal;
line-height: 1.4;
max-height: 2.8em; /* 最多显示2行 */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
/* 顶部按钮区域样式 */
.top-button-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.left-buttons {
display: flex;
align-items: center;
gap: 8px;
}
.right-buttons {
display: flex;
align-items: center;
gap: 8px;
}
/***** 附件展示样式(横向布局) *****/
.readonly-attachment .readonly-upload-list {
display: flex;
flex-direction: row;
gap: 8px;
/* margin-top: 4px; */
/* padding-bottom: 4px; */
max-height: calc(100vh - 750px); /* 调整为与容器高度一致 */
overflow-x: auto;
overflow-y: hidden;
}
.upload-file-card.readonly {
pointer-events: none;
opacity: 1;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
width: 120px; /* 调整为合适的卡片宽度 */
height: 110px; /* 增加卡片高度,确保附件名称和下载按钮显示 */
display: flex;
flex-direction: column;
border-radius: 4px;
overflow: hidden;
flex-shrink: 0; /* 防止卡片被压缩 */
}
.upload-file-card.readonly .upload-file-thumb {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
min-height: 80px; /* 增加图片区域高度,为文件名和按钮留出空间 */
background: #f8f9fa;
border-bottom: 1px solid #f0f0f0;
}
.upload-file-card.readonly .upload-thumb-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-file-card.readonly .upload-file-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: #909399;
font-size: 16px;
background: #f8f9fa;
}
.upload-file-card.readonly .upload-file-placeholder .el-icon {
font-size: 32px;
margin-bottom: 4px;
color: #c0c4cc;
}
.upload-file-card.readonly .upload-file-placeholder .file-ext {
font-size: 12px;
font-weight: 500;
color: #606266;
}
.upload-file-card.readonly .upload-file-name-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 3px 4px;
background: #fff;
flex-shrink: 0;
height: 24px; /* 增加文件名行高度,确保文件名和下载按钮显示 */
}
.upload-file-card.readonly .upload-file-name {
font-size: 11px;
color: #606266;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
margin-right: 4px;
}
.upload-file-card.readonly .download-icon {
color: #409eff;
font-size: 13px;
cursor: pointer;
padding: 2px;
border-radius: 2px;
transition: all 0.2s;
flex-shrink: 0;
}
.upload-file-card.readonly .download-icon:hover {
background-color: #f0f9ff;
color: #1890ff;
}
/***** 其余样式复用 dialog.vue *****/
.attachment-upload-container {
width: 100%;
max-height: calc(100vh - 750px);
min-height: 200px;
overflow-y: auto;
padding-right: 8px;
border: 1px solid #e4e7ed;
border-radius: 6px;
background: #fafafa;
}
/* 只读附件容器特殊样式 */
.readonly-attachment {
height: auto !important;
min-height: 120px;
max-height: calc(100vh - 750px);
padding: 4px 8px;
}
/* 优化滚动条样式,使其更明显 */
.attachment-upload-container::-webkit-scrollbar {
width: 8px;
}
.attachment-upload-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.attachment-upload-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.attachment-upload-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 附件数量提示样式 */
.attachment-count-tip {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
background: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 4px;
margin-bottom: 8px;
font-size: 13px;
color: #1890ff;
}
.attachment-count-tip .el-icon {
font-size: 14px;
}
.scroll-tip {
color: #52c41a;
font-weight: 500;
}
/* 附件上传区域内容样式 */
.attachment-upload-container .upload-demo {
padding: 12px;
min-height: 120px;
}
.attachment-upload-container .upload-demo .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.3s;
}
.attachment-upload-container .upload-demo .el-upload:hover {
border-color: #409eff;
}
.attachment-upload-container .upload-demo .el-upload__tip {
font-size: 12px;
color: #000000;
margin-top: 7px;
}
/* 调整拖拽上传图标与文字在同一行、居中对齐 */
.attachment-upload-container .upload-demo .el-upload-dragger1 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 15px;
padding: 0px !important;
}
.attachment-upload-container .upload-demo .el-upload-dragger1 .el-icon--upload {
margin: 0;
padding: 0px !important;
}
/* 文件卡片占位封面样式 */
.attachment-upload-container .upload-demo .upload-file-placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #909399;
font-size: 14px;
}
.attachment-upload-container .upload-demo .upload-file-placeholder .el-icon {
font-size: 28px;
margin-bottom: 4px;
}
.attachment-upload-container .upload-demo .upload-file-placeholder .file-ext {
font-size: 12px;
text-transform: uppercase;
text-align: center;
}
/* 让附件卡片一行展示 5 个 */
.attachment-upload-container :deep(.el-upload-list--picture-card) {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 9px;
margin-top: 12px;
padding-bottom: 16px;
}
/* 确保卡片宽度自适应 */
.attachment-upload-container :deep(.el-upload-list--picture-card) .el-upload-list__item {
margin: 0;
height: 167px !important;
width: 100% !important;
}
:deep(.el-upload-dragger) {
border: 0px !important;
padding: 0px !important;
}
:deep(.el-upload-dragger) .el-upload-dragger1 {
border: 0px !important;
padding: 0px !important;
}
/* 文件卡片样式 */
.upload-file-card {
position: relative;
width: 100%;
height: 100%;
border-radius: 6px;
overflow: hidden;
background: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s;
}
.upload-file-card:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.upload-file-thumb {
position: relative;
width: 100%;
height: 100px;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.upload-thumb-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-file-loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* background: rgba(255, 255, 255, 0.9); */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
}
.loading-text {
margin-top: 8px;
font-size: 12px;
color: #606266;
}
.upload-file-name {
padding: 6px 8px;
font-size: 11px;
color: #303133;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: #fafafa;
border-top: 1px solid #f0f0f0;
line-height: 1.2;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
word-break: break-all;
max-width: 100%;
}
.upload-file-card-actions {
display: flex;
justify-content: center;
align-items: center;
padding: 4px 6px;
background: #fafafa;
border-top: 1px solid #f0f0f0;
transition: all 0.3s;
height: 32px;
}
.upload-file-card-actions .el-button-group {
background: transparent;
border-radius: 4px;
padding: 0;
}
.upload-file-card-actions .el-button {
padding: 4px 8px;
border: 1px solid #e0e0e0;
background: #fff;
font-size: 12px;
margin: 0 2px;
border-radius: 3px;
transition: all 0.3s;
}
.upload-file-card-actions .el-button:hover {
background: #f5f5f5;
border-color: #409eff;
}
.upload-file-card-actions .el-button.is-disabled {
opacity: 0.5;
cursor: not-allowed;
background: #f5f5f5;
}
:deep(.el-card .el-card__header) {
border-bottom: 0px !important;
}
/* 请假原因tooltip样式 */
:deep(.leave-reason-tooltip) {
max-width: 400px;
word-break: break-all;
line-height: 1.5;
font-size: 13px;
padding: 8px 12px;
}
:deep(.leave-reason-tooltip .el-tooltip__content) {
text-align: left;
}
</style>