From c207301225726725572e5e9d7d9ad87722069244 Mon Sep 17 00:00:00 2001 From: dshclm <3321914460@qq.com> Date: Wed, 28 May 2025 10:42:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/newdev/leaveAudit/leaveAudit.html | 796 +++++++++++++++++- 1 file changed, 792 insertions(+), 4 deletions(-) diff --git a/ruoyi-admin/src/main/resources/templates/system/newdev/leaveAudit/leaveAudit.html b/ruoyi-admin/src/main/resources/templates/system/newdev/leaveAudit/leaveAudit.html index 566549bd..19e5eb92 100644 --- a/ruoyi-admin/src/main/resources/templates/system/newdev/leaveAudit/leaveAudit.html +++ b/ruoyi-admin/src/main/resources/templates/system/newdev/leaveAudit/leaveAudit.html @@ -1,10 +1,798 @@ <!DOCTYPE html> -<html lang="en"> +<html lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> - <meta charset="UTF-8"> - <title>Title</title> + <th:block th:include="include :: header('材料上传')"/> + <th:block th:include="include :: select2-css"/> + <th:block th:include="include :: jasny-bootstrap-css"/> + <th:block th:include="include :: element-css"/> + <link href="/file/pdf/test.css" rel="stylesheet"> + <script src="/file/pdf/tailwindcss.js"></script> + <!-- Tailwind配置 --> + <script> + tailwind.config = { + theme: { + extend: { + colors: { + primary: '#3b82f6', + secondary: '#64748b', + accent: '#0ea5e9', + neutral: '#f1f5f9', + 'neutral-dark': '#334155', + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], + }, + } + } + } + </script> + + <style type="text/tailwindcss"> + @layer utilities { + .content-auto { + content-visibility: auto; + } + .shadow-soft { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + } + .transition-custom { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } + } + .text-sm { + font-size: 1.4rem; + } + .text-base { + font-size: 1.4rem; + } + .text-xs { + font-size: 0.85rem; + } + .py-2 { + padding-top: 0.8rem; + padding-bottom: 0.8rem; + } + .space-y-3{ + margin-bottom: 1.5rem; + } + </style> </head> -<body> +<body class="bg-gray-50 font-sans"> +<div class="main-content min-h-screen flex flex-col" id="app"> + <main class="flex-grow container mx-auto px-4 py-8" style="max-width: 100%;"> + <div class="max-w-10xl mx-auto" style="width: 100%;"> + <div class="bg-white rounded-xl shadow-soft p-6 mb-8 scale-hover"> + <form class="form-horizontal space-y-6" id="form-apply-edit" th:object="${applyInfoList}"> + <input name="applyId" type="hidden" th:field="*{applyId}"/> + <h4 class="form-header h4">基本信息</h4> + <!-- 文本字段 --> + <div class="grid md:grid-cols-3 gap-6"> + <!-- 第一行前两列:姓名、性别 --> + <div class="md:col-span-1 space-y-3"> + <!-- 姓名 --> + <div class="space-y-3"> + <label for="name" class="block text-sm font-medium text-gray-700">姓名</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-user"></i> + </span> + <input disabled type="text" id="name" v-model="formData.name" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg" placeholder="请输入姓名"> + </div> + </div> + + <!-- 性别 --> + <div class="space-y-3"> + <label for="sex" class="block text-sm font-medium text-gray-700">性别</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-venus-mars"></i> + </span> + <input disabled type="text" id="sex" v-model="formData.sex" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg" placeholder="请输入性别"> + </div> + </div> + </div> + <!-- 第二行前两列:国籍、曾用名 --> + <div class="md:col-span-1 space-y-3"> + <!-- 国籍 --> + <div class="space-y-3"> + <label for="nationa" class="block text-sm font-medium text-gray-700">国籍</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-globe"></i> + </span> + <input disabled type="text" id="nationa" v-model="formData.nationa" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg" placeholder="请输入国籍"> + </div> + </div> + + <!-- 曾用名 --> + <div class="space-y-3"> + <label for="formerName" class="block text-sm font-medium text-gray-700">曾用名</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-user-tag"></i> + </span> + <input disabled type="text" id="formerName" v-model="formData.formerName" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg" placeholder="请输入曾用名"> + </div> + </div> + </div> + <!-- 第一行第三列:图片上传区域(固定位置,跨两行) --> + <div class="md:col-span-1 md:row-span-2 space-y-3 relative"> + <label class="block text-sm font-medium text-gray-700">一寸照片</label> + <div class="mt-1 h-64 border-2 border-gray-300 rounded-lg shadow-soft transition-all duration-200" style="width: 141px;height: 195px"> + <!-- 上传区域(未选中时显示) --> + <div v-if="!selectedFile" class="h-full flex justify-center items-center border-dashed cursor-pointer hover:border-primary"> + <div class="text-center"> + <i class="fa-solid fa-cloud-upload text-4xl text-gray-400 mb-2"></i> + <label for="photo" class="cursor-pointer bg-white rounded-md font-medium text-primary hover:text-primary/80"> + <span>上传照片</span> + <input disabled id="photo" name="photo" type="file" accept="image/jpeg,image/png" class="sr-only" @change="handleFileUpload"> + </label> + <p class="text-xs text-gray-500"> + JPG, PNG (最大 2MB) + </p> + </div> + </div> + <!-- 预览区域(选中后显示) --> + <div v-else class="h-full flex flex-col items-center justify-center p-3"> + <img :src="previewUrl" alt="预览图" class="max-h-full max-w-full object-cover rounded-lg mb-2" style="max-height: 200px;"> + <!-- <button type="button" @click="clearFile" class="text-xs text-red-500 hover:text-red-700">--> + <!-- <i class="fa-solid fa-times-circle mr-1"></i> 移除--> + <!-- </button>--> + </div> + </div> + </div> + <div class="space-y-3"> + <label for="nationality" class="block text-sm font-medium text-gray-700">民族</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-people-group"></i> + </span> + <input disabled type="text" id="nationality" v-model="formData.nationality" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入民族"> + </div> + </div> + <div class="space-y-3"> + <label for="maritalStatus" class="block text-sm font-medium text-gray-700">婚姻状况</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-ring"></i> + </span> + <input disabled type="text" id="maritalStatus" v-model="formData.maritalStatus" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入婚姻状况"> + + </div> + </div> + <div class="space-y-3"> + <label for="political" class="block text-sm font-medium text-gray-700">政治面貌</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-user-tie"></i> + </span> + <input disabled type="text" id="political" v-model="formData.political" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入政治面貌"> + </div> + </div> + <div class="space-y-3"> + <label for="phone" class="block text-sm font-medium text-gray-700">联系方式</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-phone"></i> + </span> + <input disabled type="text" id="phone" v-model="formData.phone" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入联系方式"> + </div> + </div> + <div class="space-y-3"> + <label for="cerno" class="block text-sm font-medium text-gray-700">身份证号</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-id-card"></i> + </span> + <input disabled type="text" id="cerno" v-model="formData.cerno" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入身份证号"> + </div> + </div> + <div class="space-y-3"> + <label for="address" class="block text-sm font-medium text-gray-700">户籍地址</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-house-chimney"></i> + </span> + <input disabled type="text" id="address" v-model="formData.address" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入户籍地址"> + </div> + </div> + <div class="space-y-3"> + <label for="registeredAuthority" class="block text-sm font-medium text-gray-700">户籍地公安机关</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-badge-shield"></i> + </span> + <input disabled type="text" id="registeredAuthority" v-model="formData.registeredAuthority" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入户籍地公安机关"> + </div> + </div> + <div class="space-y-3"> + <label for="permanentAddress" class="block text-sm font-medium text-gray-700">常住地址</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-house"></i> + </span> + <input disabled type="text" id="permanentAddress" v-model="formData.permanentAddress" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入常住地址"> + </div> + </div> + <div class="space-y-3"> + <label for="residentBureau" class="block text-sm font-medium text-gray-700">常住地公安机关</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-badge-shield"></i> + </span> + <input disabled type="text" id="residentBureau" v-model="formData.residentBureau" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入常住地公安机关"> + </div> + </div> + <div class="space-y-3"> + <label for="positionCapacity" class="block text-sm font-medium text-gray-700">单位及职务职称</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-building-user"></i> + </span> + <input disabled type="text" id="positionCapacity" v-model="formData.positionCapacity" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入单位及职务职称"> + </div> + </div> + <div class="space-y-3"> + <label for="smPost" class="block text-sm font-medium text-gray-700">已(拟)任涉密岗位</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-shield-halved"></i> + </span> + <input disabled type="text" id="smPost" v-model="formData.smPost" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入已(拟)任涉密岗位"> + </div> + </div> + <div class="space-y-3"> + <label for="smGrade" class="block text-sm font-medium text-gray-700">涉密等级</label> + <div class="relative"> + <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"> + <i class="fa-solid fa-lock"></i> + </span> + <input disabled type="text" id="smGrade" v-model="formData.smGrade" + class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors duration-200" + placeholder="请输入涉密等级"> + </div> + </div> + </div> + <h4 class="form-header h4">材料上传</h4> + <!-- 材料上传区域 - 使用循环生成 --> + <div class="mt-6" style=" display: flex;flex-wrap: wrap;justify-content: space-around;"> + <div v-for="(material, index) in materials" :key="index" class="border border-gray-200 rounded-lg p-4 mb-6" style="width: 50%;"> + <div class="flex items-center justify-between mb-3"> + <span class="text-sm text-gray-600">{{ material.description }}</span> + <div class="relative"> + <label :for="'fileUpload' + index" class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary cursor-pointer transition-colors duration-200"> + <i class="fa-solid fa-upload mr-2"></i> 选择文件 + <input :id="'fileUpload' + index" type="file" class="sr-only" :accept="material.accept" @change="handleFileSelection(index)"> + </label> + <a v-if="material.code ==='bm_user_lpromise_012'" :href="material.templateUrl" :download="material.downloadName" class="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-colors duration-200"> + <i class="fa-solid fa-download mr-1"></i> 下载模版 + </a> + </div> + </div> + + <!-- 上传进度条 --> + <div v-if="uploadProgress[index] > 0 && uploadProgress[index] < 100" class="mb-3"> + <div class="flex justify-between text-sm mb-1"> + <span class="text-gray-700">上传中: {{ currentFileName[index] }}</span> + <span class="text-gray-500">{{ uploadProgress[index] }}%</span> + </div> + <div class="w-full bg-gray-200 rounded-full h-2"> + <div class="bg-primary h-2 rounded-full" :style="{ width: uploadProgress[index] + '%' }"></div> + </div> + </div> + + <!-- 文件列表 --> + <div class="mt-3"> + <div v-if="uploadedFiles[index].length === 0" class="py-6 text-center border-2 border-dashed border-gray-300 rounded-lg"> + <i class="fa-solid fa-cloud-upload-alt text-3xl text-gray-400 mb-2"></i> + <p class="text-gray-500">未上传文件</p> + </div> + <div v-else class="space-y-2"> + <div v-for="(file, fileIndex) in uploadedFiles[index]" :key="fileIndex" class="flex items-center p-2 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors duration-200"> + <div class="flex-shrink-0 mr-3"> + <i :class="getFileIcon(file.fileName)" class="text-xl"></i> + </div> + <div class="flex-grow min-w-0"> + <p style="height: 100%;line-height: 20px" class="text-sm font-medium text-gray-900 truncate">{{ file.fileName }}</p> + </div> + <div class="flex-shrink-0 ml-3"> + <button v-if="isImageFile(file.fileName)" @click="previewFile(file)" class="text-primary hover:text-primary/80 mr-2"> + <i class="fa-solid fa-eye"></i> + </button> + <button @click="downloadFile(file, index)" class="text-primary hover:text-primary/80 mr-2"> + <i class="fa-solid fa-download"></i> + </button> +<!-- <button @click.stop="deleteFile(file, index, fileIndex)" class="text-red-500 hover:text-red-700">--> +<!-- <i class="fa-solid fa-trash"></i>--> +<!-- </button>--> + </div> + </div> + </div> + </div> + </div> + </div> + </form> + + <div class="row mt-6"> + <div class="col-sm-offset-5 col-sm-10" style="display: flex;"> + <button type="button" class="btn btn-sm btn-primary" @click="approveApplication"><i class="fa fa-check"></i>通 过</button> + <button type="button" class="btn btn-sm btn-danger" @click="showRejectDialog"><i class="fa fa-undo"></i>退 回</button> + <button type="button" class="btn btn-sm btn-danger" onclick="closeItem()"><i class="fa fa-reply-all"></i>关 闭 + </button> + </div> + </div> + </div> + <!-- 退回意见对话框 --> + <div v-if="rejectDialogVisible" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> + <div class="bg-white rounded-lg max-w-md w-full p-6 transform transition-all"> + <h3 class="text-lg font-medium text-gray-900 mb-4">退回申请</h3> + <div class="mb-4"> + <label for="rejectReason" class="block text-sm font-medium text-gray-700 mb-1">退回原因</label> + <textarea + id="rejectReason" + v-model="rejectReason" + rows="4" + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" + placeholder="请输入退回原因..." + ></textarea> + </div> + <div class="flex justify-end space-x-3"> + <button @click="rejectDialogVisible = false" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"> + 取消 + </button> + <button @click="rejectApplication" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90"> + 确认退回 + </button> + </div> + </div> + </div> + <!-- 上传成功提示 --> + <div v-if="uploadSuccess" class="fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg transform transition-all duration-500 translate-y-0 opacity-100"> + <div class="flex items-center"> + <i class="fa-solid fa-check-circle mr-2"></i> + <span>{{ uploadSuccessMessage }}</span> + </div> + </div> + <!-- 图片预览模态框 --> + <div v-if="previewModalVisible" class="fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50"> + <div class="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] overflow-auto"> + <div class="p-4 flex justify-between items-center border-b"> + <h3 class="text-lg font-medium text-gray-900">{{ previewingFile.fileName }}</h3> + <button @click="closePreviewModal" class="text-gray-500 hover:text-gray-700"> + <i class="fa-solid fa-times"></i> + </button> + </div> + <div class="p-4 flex justify-center"> + <img :src="getPreviewUrl(previewingFile)" alt="预览图" class="max-w-full max-h-[70vh] object-contain"> + </div> + <div class="p-4 border-t flex justify-end"> + <button @click="closePreviewModal" class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md shadow-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-colors duration-200"> + <i class="fa-solid fa-times mr-2"></i> 关闭 + </button> + </div> + </div> + </div> + </div> + </main> +</div> +<th:block th:include="include :: footer"/> +<th:block th:include="include :: select2-js"/> +<th:block th:include="include :: jasny-bootstrap-js"/> +<th:block th:include="include :: element-js"/> +<script type="text/javascript" th:inline="javascript"> + var prefix = ctx + "system/leaveAuditList"; + new Vue({ + el: '#app', + data: { + formData: { + name: '', + sex: '', + nationa: '', + formerName: '', + nationality: '', + maritalStatus: '', + political: '', + phone: '', + cerno: '', + address: '', + registeredAuthority: '', + permanentAddress: '', + residentBureau: '', + positionCapacity: '', + smPost: '', + smGrade: '', + photoUrl: '', + applyId: '', + }, + selectedFile: null, + // 材料配置 - 定义六个材料的信息 + materials: [ + { + name: '涉密人员离职离岗保密审批表', + code: 'bm_user_userleav_011', + templateUrl: '/file/pdfFiles/涉密人员离职离岗保密审批表.pdf', + downloadName: '涉密人员离职离岗保密审批表.pdf', + description: '涉密人员离职离岗保密审批表', + accept: '.pdf, .doc, .docx, .jpg, .jpeg, .png, .gif, .bmp' + }, + { + name: '涉密人员离职离岗保密承诺书', + code: 'bm_user_lpromise_012', + templateUrl: '/file/pdfFiles/涉密人员离职离岗保密承诺书.pdf', + downloadName: '涉密人员离职离岗保密承诺书.pdf', + description: '涉密人员离职离岗保密承诺书', + accept: '.pdf, .doc, .docx, .jpg, .jpeg, .png, .gif, .bmp' + }, + ], + // 文件上传相关数据 + selectedFiles: Array(2).fill(null), + currentFileName: Array(2).fill(''), + uploadProgress: Array(2).fill(0), + uploadedFiles: Array(2).fill([]).map(() => []), + previewModalVisible: false, + previewingFile: {}, + uploadSuccess: false, + uploadSuccessMessage: '', + prefix: ctx + "system/leaveAuditList", + isLoading: false, + showResult: false, + pdfUrl:'', + rejectDialogVisible: false, + rejectReason: '', + }, + computed: { + previewUrl() { + return this.selectedFile ? window.URL.createObjectURL(this.selectedFile) : ''; + } + }, + methods: { + handleFileUpload(event) { + let file = event.target.files[0]; + if (file) { + // 检查文件类型和大小 + let fileSize = file.size / 1024 / 1024; // MB + if (fileSize > 2) { + this.$message.error('文件大小不能超过2MB'); + return; + } + let fileType = file.type; + if (!fileType.startsWith('image/')) { + this.$message.error('请上传图片文件'); + return; + } + // 验证图片格式 + let validFormats = ['image/jpeg', 'image/png']; + if (!validFormats.includes(fileType)) { + this.$message.error('请上传JPG或PNG格式的图片'); + return; + } + // 验证图片尺寸是否接近一寸照片 (2.5cm x 3.5cm, 约295x413像素) + let img = new Image(); + img.src = window.URL.createObjectURL(file); + img.onload = () => { + let width = img.naturalWidth; + let height = img.naturalHeight; + let ratio = width / height; + // 检查宽高比是否接近一寸照片的比例 (约0.714) + if (Math.abs(ratio - 0.714) > 0.1) { + this.$message({ + message: '建议上传一寸照片尺寸 (约295x413像素)', + type: 'warning' + }); + } + this.selectedFile = file; + }; + img.onerror = () => { + this.$message.error('无法加载图片,请重新选择'); + }; + } + }, + clearFile() { + this.selectedFile = null; + document.getElementById('photo').value = ''; + }, + // 文件上传处理 + handleFileSelection(index) { + this.selectedFiles[index] = event.target.files[0]; + if (this.selectedFiles[index]) { + let file = this.selectedFiles[index]; + let fileExtension = file.name.split('.').pop().toLowerCase(); + + // 根据材料类型验证文件格式 + let validExtensions = this.materials[index].accept.split(',').map(ext => ext.trim().replace('.', '')); + + if (!validExtensions.includes(fileExtension)) { + this.$message.error(`请上传${this.materials[index].description.split(':')[1]}`); + document.getElementById(`fileUpload${index}`).value = ''; // 清空选择 + return; + } + + this.uploadFile(file, index); + } + }, +// 新增通过申请方法 + approveApplication() { + if (this.uploadedFiles.length === 0) { + alert('请上传所需材料'); + return; + } + this.$confirm('确认提交吗?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + $.operate.saveTab(`${this.prefix}/leaveAudit`,{applyId:this.formData.applyId}); + }).catch(() => { + this.$message({ + type: 'info', + message: '已取消' + }); + }); + }, + // 新增显示退回对话框方法 + showRejectDialog() { + if (this.uploadedFiles.length === 0) { + alert('请上传所需材料'); + return; + } + + this.rejectDialogVisible = true; + this.rejectReason = ''; + }, + // 新增退回申请方法 + rejectApplication() { + if (!this.rejectReason.trim()) { + alert('请输入退回原因'); + return; + } + + $.operate.saveTab(`${this.prefix}/goBackLeave`,{ + applyId:this.formData.applyId, + leaveMsg:this.rejectReason + }); + }, + uploadFile(file, index) { + this.currentFileName[index] = file.name; + this.uploadProgress[index] = 0; + + let formData = new FormData(); + formData.append('applyId', this.formData.applyId || ''); + formData.append('filename', file.name); + formData.append('fileCode', this.materials[index].code); + formData.append('file', file); + + axios.post(ctx + 'system/file/upload', formData, { + onUploadProgress: (progressEvent) => { + this.uploadProgress[index] = Math.round((progressEvent.loaded * 100) / progressEvent.total); + } + }) + .then(response => { + if (response.data.code === web_status.SUCCESS) { + // 显示上传成功提示 + this.uploadSuccess = true; + this.uploadSuccessMessage = `文件 "${file.name}" 上传成功`; + + // 3秒后隐藏提示 + setTimeout(() => { + this.uploadSuccess = false; + }, 3000); + + // 更新文件列表 + let fileInfo = { + id: response.data.data.fileId, + fileName: file.name, + fileSize: file.size, + fileType: file.type, + fileUrl: response.data.data.filePath, + uploadTime: new Date().toISOString() + }; + this.$set(this.uploadedFiles, index, [...this.uploadedFiles[index], fileInfo]); + // 重置选择框 + document.getElementById(`fileUpload${index}`).value = ''; + } else { + this.$message.error(`上传失败: ${response.data.msg}`); + } + }) + .catch(error => { + console.error('上传错误:', error); + this.$message.error('上传过程中发生错误'); + }); + }, + downloadFile(file, index) { + // 直接通过接口下载 + axios.get(ctx + 'system/file/download',{ + params: { fileId: file.id }, + responseType: 'blob', // 重要:指定响应类型为blob + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => { + let url = window.URL.createObjectURL(new Blob([response.data])); + let link = document.createElement('a'); + link.href = url; + link.setAttribute('download', file.fileName); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }) + .catch(error => { + console.error('下载错误:', error); + this.$message.error('下载文件时出错'); + }); + }, + + + deleteFile(file, materialIndex, fileIndex) { + let event = window.event; + if (event) { + event.preventDefault(); + } + this.$confirm(`确定要删除文件 "${file.fileName}" 吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + axios.get(ctx + 'system/file/delFile/' + file.id) + .then(response => { + if (response.data.code === web_status.SUCCESS) { + this.uploadedFiles[materialIndex].splice(fileIndex, 1); + this.$message.success('文件已成功删除'); + } else { + this.$message.error(`删除失败: ${response.data.msg}`); + } + }) + .catch(error => { + console.error('删除错误:', error); + this.$message.error('删除文件时出错'); + }); + }).catch(() => { + this.$message({ + type: 'info', + message: '已取消删除' + }); + }); + }, + submitHandler() { + if (this.uploadedFiles.length == 0) { + this.$message.warning('请上传所需材料'); + return; + } + + this.$confirm('确认提交吗?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + $.operate.saveTab(`${this.prefix}/submitLeave?applyId=${this.formData.applyId}`); + }).catch(() => { + this.$message({ + type: 'info', + message: '已取消' + }); + }); + }, + // 获取已上传文件 + getUploadedFiles() { + if (!this.formData.applyId) return; + + // 使用forEach确保每个请求有自己的作用域 + this.materials.forEach((material, index) => { + axios.get(ctx + 'system/file/getFileInfo', { + params: { + applyId: this.formData.applyId, + fileCode: material.code, + } + }) + .then(response => { + if (response.data.code === web_status.SUCCESS) { + // 使用Vue.set确保响应式更新 + this.$set(this.uploadedFiles, index, response.data.data.map((file) => { + return { + id: file.fileId, + fileName: file.fileName, + fileSize: file.fileSize, + fileType: file.fileType, + fileUrl: file.filePath, + uploadTime: file.createTime + } + })); + } else { + console.error(`获取${material.name}文件列表失败:`, response.data.msg); + this.$message.error(`获取${material.name}已上传文件失败: ${response.data.msg}`); + } + }) + .catch(error => { + console.error(`获取${material.name}文件列表错误:`, error); + this.$message.error(`获取${material.name}已上传文件时出错`); + }); + }); + }, + + // 图片预览相关方法 + isImageFile(fileName) { + const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp','pdf']; + const extension = fileName.split('.').pop().toLowerCase(); + return imageExtensions.includes(extension); + }, + + getFileIcon(fileName) { + const extension = fileName.split('.').pop().toLowerCase(); + if (extension === 'pdf') return 'fa-solid fa-file-pdf text-red-500'; + if (['doc', 'docx'].includes(extension)) return 'fa-solid fa-file-word text-blue-500'; + if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(extension)) return 'fa-solid fa-file-image text-green-500'; + return 'fa-solid fa-file text-gray-500'; + }, + + previewFile(file) { + let extension = file.fileName.split('.').pop().toLowerCase(); + if (extension === 'pdf') { + // 使用preventDefault阻止默认行为,避免影响当前页面状态 + let event = window.event; + if (event) { + event.preventDefault(); + } + window.open(file.fileUrl, '_blank'); + return + } + this.previewingFile = file; + this.previewModalVisible = true; + }, + + closePreviewModal() { + this.previewModalVisible = false; + this.previewingFile = {}; + }, + + getPreviewUrl(file) { + // 如果是图片,直接返回URL + if (this.isImageFile(file.fileName)) { + return ctx + 'system/file/download?fileId=' + file.id; + } + return ''; + } + }, + mounted() { + // 从后端获取转义后的 JSON 字符串 + if ([[${applyInfoList}]]){ + this.formData = {...[[${applyInfoList}]]} + // 获取图片并转换为 Blob + if (this.formData.photoUrl) { + fetch(this.formData.photoUrl) + .then(response => response.blob()) + .then(blob => { + this.selectedFile = blob; + }) + .catch(error => console.error('处理图片时出错:', error)); + } + } + // 获取已上传文件 + this.getUploadedFiles(); + } + }); +</script> </body> </html> \ No newline at end of file