1560 lines
45 KiB
Vue
1560 lines
45 KiB
Vue
<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>
|