| | |
| | | |
| | | |
| | | |
| | | <!-- 操作按钮区 --> |
| | | <view class="action-buttons" v-if="this.current"> |
| | | <button class="secondary-btn" @click="handleEmergencyRelease">紧急放行</button> |
| | | <button class="secondary-btn" @click="handleWithdraw">撤回</button> |
| | | <button class="secondary-btn" @click="getInspectionItems">获取检验项目</button> |
| | | </view> |
| | | |
| | | |
| | | |
| | |
| | | </table> |
| | | </view> |
| | | |
| | | <!-- 操作按钮区 --> |
| | | <view class="action-buttons"> |
| | | <button class="secondary-btn" @click="addDestruction" v-if="this.current">破坏实验</button> |
| | | <button class="secondary-btn" @click="uploadImages">上传/查看图片</button> |
| | | <button class="secondary-btn" @click="fetchDrawingNumber">调取PLM图纸</button> |
| | | <button class="secondary-btn" @click="viewAttachmentInfo">查看附件信息</button> |
| | | <button class="secondary-btn" @click="addDefectDescription" v-if="this.current">添加不良描述</button> |
| | | <button class="primary-btn" @click="submitInspection" v-if="this.current">检验提交</button> |
| | | <!-- 页面内容区域 --> |
| | | <view class="content-wrapper"> |
| | | <!-- 为底部按钮留出空间 --> |
| | | </view> |
| | | |
| | | <!-- 固定在底部的操作按钮区 --> |
| | | <view class="fixed-action-buttons"> |
| | | <button class="action-btn" @click="handleEmergencyRelease" v-if="this.current">紧急放行</button> |
| | | <button class="action-btn" @click="handleWithdraw" v-if="this.current">撤回</button> |
| | | <button class="action-btn" @click="getInspectionItems" v-if="this.current">获取检验项目</button> |
| | | <button class="action-btn" @click="addDestruction" v-if="this.current">破坏实验</button> |
| | | <button class="action-btn" @click="uploadImages">上传/查看图片</button> |
| | | <button class="action-btn" @click="fetchDrawingNumber">调取PLM图纸</button> |
| | | <button class="action-btn" @click="viewAttachmentInfo">查看附件信息</button> |
| | | <button class="action-btn" @click="addDefectDescription" v-if="this.current">添加不良描述</button> |
| | | <button class="action-btn primary" @click="submitInspection" v-if="this.current">检验提交</button> |
| | | </view> |
| | | <view v-if="remarksPopup" class="overlay"> |
| | | <view class="popup"> |
| | |
| | | |
| | | <view class="barcode"> |
| | | <u-modal :show="drawingShow" title="图纸明细" @confirm="drawingConfirm" @cancel="drawingCancel" |
| | | showCancelButton> |
| | | <uni-table border stripe emptyText="暂无更多数据" style="margin-left: 5px;margin-right: 5px;height: 500px;"> |
| | | showCancelButton :z-index="1000"> |
| | | <uni-table border stripe emptyText="暂无更多数据" style="margin-left: 5px;margin-right: 5px;height: 400px;max-height: 60vh;overflow-y: auto;"> |
| | | <uni-tr> |
| | | <uni-th align="center">相关文档</uni-th> |
| | | <uni-th align="center" width="90">有无关联PDF文件</uni-th> |
| | |
| | | <div class="attachment-detail-row"><span class="attachment-label">受控日期:</span><span>{{ selectedAttachment.fdate }}</span></div> |
| | | <div class="attachment-detail-row"><span class="attachment-label">上传人:</span><span>{{ selectedAttachment.createBy }}</span></div> |
| | | <div class="attachment-detail-row"><span class="attachment-label">上传时间:</span><span>{{ selectedAttachment.createDate }}</span></div> |
| | | <div v-if="isPreviewable(selectedAttachment.fattach)" class="attachment-preview-area"> |
| | | <div v-if="['pdf','jpg','jpeg','png','gif'].includes(selectedAttachment.fattach.trim().split('.').pop().toLowerCase())"> |
| | | <iframe :src="getAttachmentUrl(selectedAttachment)" style="width:100%;height:320px;border-radius:10px;background:#f8fafc;" frameborder="0"></iframe> |
| | | </div> |
| | | <div v-else-if="['txt'].includes(selectedAttachment.fattach.trim().split('.').pop().toLowerCase())"> |
| | | <iframe :src="getAttachmentUrl(selectedAttachment)" style="width:100%;height:320px;border-radius:10px;background:#f8fafc;" frameborder="0"></iframe> |
| | | </div> |
| | | <div v-else-if="['doc','docx','xls','xlsx'].includes(selectedAttachment.fattach.trim().split('.').pop().toLowerCase())"> |
| | | <iframe :src="'https://view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(getAttachmentUrl(selectedAttachment))" style="width:100%;height:320px;border-radius:10px;background:#f8fafc;" frameborder="0"></iframe> |
| | | </div> |
| | | </div> |
| | | <div v-else class="attachment-download-area"> |
| | | <button class="attachment-download-link" @click="downloadAttachment(selectedAttachment)">下载附件</button> |
| | | <div class="attachment-actions-detail"> |
| | | <button class="attachment-action-btn preview-btn" |
| | | @click="previewFtpFile(selectedAttachment)" |
| | | v-if="isPreviewable(selectedAttachment.fattach)"> |
| | | 🔍 在线预览 |
| | | </button> |
| | | <button class="attachment-action-btn download-btn" |
| | | @click="downloadAttachment(selectedAttachment)"> |
| | | 📥 下载文件 |
| | | </button> |
| | | </div> |
| | | </div> |
| | | <div v-else class="attachment-detail-empty">暂无附件信息</div> |
| | |
| | | <div v-else-if="attachments.length === 0">暂无附件</div> |
| | | <ul class="attachment-list" v-else> |
| | | <li v-for="item in attachments" :key="item.id"> |
| | | <span class="attachment-name" @click="showAttachmentDetailDialog(item)"> |
| | | {{ item.fattach }} |
| | | </span> |
| | | <div class="attachment-info"> |
| | | <span class="attachment-name" @click="showAttachmentDetailDialog(item)"> |
| | | {{ item.fattach }} |
| | | </span> |
| | | <div class="attachment-meta"> |
| | | <span class="attachment-type">{{ item.ftype || '未知类型' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="attachment-actions"> |
| | | <button class="secondary-btn" @click="showAttachmentDetailDialog(item)">详情</button> |
| | | <button class="secondary-btn preview-btn" @click="previewFtpFile(item)" |
| | | v-if="isPreviewable(item.fattach)">预览</button> |
| | | <button class="secondary-btn" @click="downloadAttachment(item)">下载</button> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <button class="attachment-popup-close" @click="closeAttachmentPopup">关闭</button> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 文件预览弹窗 --> |
| | | <view v-if="showFilePreviewPopup" class="overlay"> |
| | | <view class="popup file-preview-popup"> |
| | | <h3 class="file-preview-title">{{ previewTitle }}</h3> |
| | | <div class="file-preview-divider"></div> |
| | | <div class="file-preview-content"> |
| | | <!-- 文本内容预览 --> |
| | | <pre v-if="previewType === 'text'">{{ previewContent }}</pre> |
| | | |
| | | <!-- 图片内容预览 --> |
| | | <view v-else-if="previewType === 'image'" class="image-preview-container"> |
| | | <image :src="previewContent" mode="widthFix" style="width: 100%; max-height: 400px;"></image> |
| | | </view> |
| | | |
| | | <!-- Excel 等 Office 文件提示 --> |
| | | <view v-else-if="previewType === 'excel'" class="unsupported-preview"> |
| | | <view class="unsupported-icon">📊</view> |
| | | <view class="unsupported-text">Excel 文件暂不支持在线预览</view> |
| | | <view class="unsupported-hint">请点击下载按钮获取完整文件</view> |
| | | </view> |
| | | |
| | | <!-- 不支持的文件类型 --> |
| | | <view v-else class="unsupported-preview"> |
| | | <view class="unsupported-icon">📄</view> |
| | | <view class="unsupported-text">此文件格式暂不支持预览</view> |
| | | <view class="unsupported-hint">请点击下载按钮获取完整文件</view> |
| | | </view> |
| | | </div> |
| | | <div class="file-preview-actions"> |
| | | <button v-if="previewType !== 'text'" class="file-preview-btn download-btn" @click="downloadPreviewFile">📥 下载文件</button> |
| | | <button class="file-preview-btn close-btn" @click="closeFilePreview">关闭</button> |
| | | </div> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | attachmentsLoading: false, |
| | | selectedAttachment: null, |
| | | showAttachmentDetail: false, |
| | | showFilePreviewPopup: false, |
| | | previewContent: '', |
| | | previewTitle: '', |
| | | previewItemNo: '', |
| | | previewType: '', // 'text', 'image', 'excel', 'unsupported' |
| | | |
| | | } |
| | | }, |
| | |
| | | this.attachmentsLoading = false; |
| | | if (res.status === 0) { |
| | | this.attachments = res.data.tbBillList; |
| | | // 为每个附件设置默认可用状态 |
| | | this.attachments.forEach((item, index) => { |
| | | this.$set(item, 'ftpAvailable', true); // 默认认为文件可用 |
| | | this.$set(item, 'checking', false); |
| | | }); |
| | | } else if (res.status === 1 && res.message === "该检验单未上传附件信息!") { |
| | | uni.showToast({ title: res.message, icon: "none" }); |
| | | } else { |
| | |
| | | this.showAttachmentPopup = false; |
| | | }, |
| | | getAttachmentUrl(item) { |
| | | const baseUrl = "http://192.168.1.22:10054"; |
| | | // 去除所有空白字符(包括中英文空格、制表符等) |
| | | let fileName = item.fattach.replace(/[\s\u3000]+/g, '').trim(); |
| | | return baseUrl + "/api/LLJ/DownloadAttachment?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName); |
| | | let fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim(); |
| | | // 统一使用FTP下载接口,包含FTP服务器地址 |
| | | return this.$store.state.serverInfo.serverAPI + "/LLJ/DownloadFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer); |
| | | }, |
| | | showAttachmentDetailDialog(item) { |
| | | console.log('查看详情', item); |
| | |
| | | const ext = filename.trim().split('.').pop().toLowerCase(); |
| | | // 支持在线预览的文件类型 |
| | | return [ |
| | | 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'txt', 'doc', 'docx', 'xls', 'xlsx' |
| | | 'pdf', // PDF文件 |
| | | 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', // 图片文件 |
| | | 'txt', 'log', 'md', // 文本文件 |
| | | 'doc', 'docx', // Word文档 |
| | | 'xls', 'xlsx', // Excel表格 |
| | | 'ppt', 'pptx', // PowerPoint演示文稿 |
| | | 'csv' // CSV文件 |
| | | ].includes(ext); |
| | | }, |
| | | // 处理附件下载错误 |
| | |
| | | showCancel: false |
| | | }); |
| | | }, |
| | | downloadAttachment(item) { |
| | | const baseUrl = "http://192.168.1.22:10054"; |
| | | // 去除所有空格、全角空格、回车、换行 |
| | | // 预览FTP文件 |
| | | previewFtpFile(item) { |
| | | const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim(); |
| | | const url = baseUrl + "/api/Llj/DownloadFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName); |
| | | uni.downloadFile({ |
| | | const fileExt = fileName.split('.').pop().toLowerCase(); |
| | | |
| | | // 检查文件类型是否支持预览 |
| | | if (!this.isPreviewable(fileName)) { |
| | | uni.showModal({ |
| | | title: '不支持预览', |
| | | content: '该文件类型不支持在线预览,请下载后查看', |
| | | showCancel: false |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | const previewUrl = this.$store.state.serverInfo.serverAPI + "/LLJ/PreviewFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer); |
| | | |
| | | // 根据文件类型进行不同的预览处理 |
| | | if (['pdf'].includes(fileExt)) { |
| | | this.previewPdfFile(previewUrl, fileName); |
| | | } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(fileExt)) { |
| | | this.previewImageFile(previewUrl, fileName); |
| | | } else if (['txt'].includes(fileExt)) { |
| | | this.previewTextFile(previewUrl, fileName); |
| | | } else if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(fileExt)) { |
| | | this.previewOfficeFile(previewUrl, fileName); |
| | | } else { |
| | | // 尝试通用预览 |
| | | this.previewGenericFile(previewUrl, fileName); |
| | | } |
| | | }, |
| | | |
| | | // 预览PDF文件 |
| | | previewPdfFile(url, fileName) { |
| | | // 先下载PDF文件,转为base64后预览 |
| | | uni.request({ |
| | | url: url, |
| | | method: 'GET', |
| | | responseType: 'arraybuffer', |
| | | success: (res) => { |
| | | if (res.statusCode === 200) { |
| | | if (typeof plus !== 'undefined' && plus.runtime && plus.runtime.openFile) { |
| | | plus.runtime.openFile({ path: res.tempFilePath }, () => { |
| | | uni.showToast({ title: '打开成功', icon: 'success' }); |
| | | }, (e) => { |
| | | uni.showModal({ title: '提示', content: '文件下载成功,但无法自动打开。请在文件管理中手动查找并打开。', showCancel: false }); |
| | | }); |
| | | } else { |
| | | uni.showModal({ title: '提示', content: '文件下载成功,但当前环境无法自动打开。请在文件管理中手动查找并打开。', showCancel: false }); |
| | | } |
| | | const base64Data = uni.arrayBufferToBase64(res.data); |
| | | // 存储到全局变量 |
| | | getApp().globalData.tempPDF = base64Data; |
| | | uni.navigateTo({ |
| | | url: `/pages/fileView/pdfView` |
| | | }); |
| | | } else { |
| | | uni.showModal({ title: '下载失败', content: `下载失败,状态码:${res.statusCode}`, showCancel: false }); |
| | | this.handlePreviewError(res.statusCode, fileName); |
| | | } |
| | | }, |
| | | fail: (error) => { |
| | | uni.showModal({ title: '下载失败', content: `下载失败,请检查网络连接。${error.errMsg}`, showCancel: false }); |
| | | this.handlePreviewError(0, fileName, error.errMsg); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | // 预览图片文件 |
| | | previewImageFile(url, fileName) { |
| | | // #ifdef APP-PLUS |
| | | // APP环境:先下载到本地再预览,避免网络图片加载问题 |
| | | uni.showLoading({ title: '加载图片...' }); |
| | | uni.downloadFile({ |
| | | url: url, |
| | | success: (res) => { |
| | | uni.hideLoading(); |
| | | if (res.statusCode === 200) { |
| | | // 使用本地临时路径 |
| | | uni.navigateTo({ |
| | | url: `/pages/fileView/imageView?url=${encodeURIComponent(res.tempFilePath)}` |
| | | }); |
| | | } else { |
| | | this.handlePreviewError(res.statusCode, fileName); |
| | | } |
| | | }, |
| | | fail: (error) => { |
| | | uni.hideLoading(); |
| | | this.handlePreviewError(0, fileName, error.errMsg); |
| | | } |
| | | }); |
| | | // #endif |
| | | |
| | | // #ifdef H5 || MP |
| | | // H5和小程序:直接使用网络URL |
| | | uni.navigateTo({ |
| | | url: `/pages/fileView/imageView?url=${encodeURIComponent(url)}` |
| | | }); |
| | | // #endif |
| | | }, |
| | | |
| | | // 预览文本文件 |
| | | previewTextFile(url, fileName) { |
| | | // 文本文件直接显示在弹窗中 |
| | | uni.showLoading({ title: '加载文件内容...' }); |
| | | uni.request({ |
| | | url: url, |
| | | method: 'GET', |
| | | success: (res) => { |
| | | uni.hideLoading(); |
| | | if (res.statusCode === 200) { |
| | | const fileType = this.getFileType(fileName); |
| | | |
| | | if (fileType === 'text') { |
| | | // 文本文件:显示内容 |
| | | this.showFilePreview(res.data, fileName); |
| | | } else if (fileType === 'image') { |
| | | // 图片文件:显示图片URL |
| | | this.showFilePreview(url, fileName); |
| | | } else { |
| | | // 其他文件类型:显示提示信息 |
| | | this.showFilePreview('', fileName); |
| | | } |
| | | } else { |
| | | this.handlePreviewError(res.statusCode, fileName); |
| | | } |
| | | }, |
| | | fail: (error) => { |
| | | uni.hideLoading(); |
| | | this.handlePreviewError(0, fileName, error.errMsg); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | // 检测文件类型 |
| | | getFileType(fileName) { |
| | | const fileExt = fileName.split('.').pop().toLowerCase(); |
| | | |
| | | if (['txt', 'log', 'md', 'csv', 'json', 'xml'].includes(fileExt)) { |
| | | return 'text'; |
| | | } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExt)) { |
| | | return 'image'; |
| | | } else if (['xls', 'xlsx', 'doc', 'docx', 'ppt', 'pptx'].includes(fileExt)) { |
| | | return 'excel'; |
| | | } else { |
| | | return 'unsupported'; |
| | | } |
| | | }, |
| | | |
| | | // 显示文件预览弹窗 |
| | | showFilePreview(content, fileName) { |
| | | this.previewContent = content; |
| | | this.previewTitle = fileName; |
| | | this.previewItemNo = this.selectedAttachment?.itemNo || ''; |
| | | this.previewType = this.getFileType(fileName); |
| | | this.showFilePreviewPopup = true; |
| | | }, |
| | | |
| | | // 关闭文件预览弹窗 |
| | | closeFilePreview() { |
| | | this.showFilePreviewPopup = false; |
| | | this.previewContent = ''; |
| | | this.previewTitle = ''; |
| | | this.previewItemNo = ''; |
| | | this.previewType = ''; |
| | | }, |
| | | |
| | | // 下载预览文件 |
| | | downloadPreviewFile() { |
| | | const item = { fattach: this.previewTitle, itemNo: this.previewItemNo }; |
| | | this.downloadAttachment(item); |
| | | this.closeFilePreview(); |
| | | }, |
| | | |
| | | // 预览Office文件 |
| | | previewOfficeFile(url, fileName) { |
| | | // 先检查Excel文件,使用专门的Excel预览页面 |
| | | const fileExt = fileName.split('.').pop().toLowerCase(); |
| | | if (['xls', 'xlsx'].includes(fileExt)) { |
| | | // Excel文件预览 |
| | | uni.request({ |
| | | url: url, |
| | | method: 'GET', |
| | | responseType: 'arraybuffer', |
| | | success: (res) => { |
| | | if (res.statusCode === 200) { |
| | | const base64Data = uni.arrayBufferToBase64(res.data); |
| | | // 存储 Base64 数据到本地存储 |
| | | uni.setStorageSync('excelBase64Data', base64Data); |
| | | uni.navigateTo({ |
| | | url: `/pages/fileView/excelView` |
| | | }); |
| | | } else { |
| | | this.handlePreviewError(res.statusCode, fileName); |
| | | } |
| | | }, |
| | | fail: (error) => { |
| | | this.handlePreviewError(0, fileName, error.errMsg); |
| | | } |
| | | }); |
| | | } else if (['doc', 'docx'].includes(fileExt)) { |
| | | // Word文件,尝试使用Word预览页面或者微软在线预览 |
| | | try { |
| | | const officePreviewUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}`; |
| | | // 如果有webView页面,使用webView预览 |
| | | this.previewGenericFile(officePreviewUrl, fileName); |
| | | } catch (error) { |
| | | this.handlePreviewError(0, fileName, '不支持此Office文件类型的预览'); |
| | | } |
| | | } else { |
| | | // 其他Office文件,使用微软在线预览服务 |
| | | const officePreviewUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}`; |
| | | this.previewGenericFile(officePreviewUrl, fileName); |
| | | } |
| | | }, |
| | | |
| | | // 通用文件预览 |
| | | previewGenericFile(url, fileName) { |
| | | // 由于没有通用的webView页面,显示提示并提供下载 |
| | | uni.showModal({ |
| | | title: '文件预览', |
| | | content: `文件 "${fileName}" 需要下载后查看,是否立即下载?`, |
| | | showCancel: true, |
| | | confirmText: '下载', |
| | | cancelText: '取消', |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | const item = { fattach: fileName, itemNo: this.selectedAttachment.itemNo }; |
| | | this.downloadAttachment(item); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | // 处理预览错误 |
| | | handlePreviewError(statusCode, fileName, errorMsg = '') { |
| | | let message = ''; |
| | | if (statusCode === 404) { |
| | | message = `文件 ${fileName} 在FTP服务器上不存在`; |
| | | } else if (statusCode === 0) { |
| | | message = `预览失败:${errorMsg}`; |
| | | } else { |
| | | message = `预览失败,状态码:${statusCode}`; |
| | | } |
| | | |
| | | uni.showModal({ |
| | | title: '预览失败', |
| | | content: message, |
| | | showCancel: true, |
| | | confirmText: '下载', |
| | | cancelText: '取消', |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | // 用户选择下载文件 |
| | | const item = { fattach: fileName, itemNo: this.selectedAttachment.itemNo }; |
| | | this.downloadAttachment(item); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | downloadAttachment(item) { |
| | | // 去除所有空格、全角空格、回车、换行 |
| | | const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim(); |
| | | // 使用配置的服务器地址和FTP服务器地址 |
| | | const url = this.$store.state.serverInfo.serverAPI + "/LLJ/DownloadFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer); |
| | | |
| | | // 检查运行环境 |
| | | // #ifdef H5 |
| | | // H5环境:使用浏览器下载 |
| | | this.downloadFileInBrowser(url, fileName); |
| | | // #endif |
| | | |
| | | // #ifdef APP-PLUS |
| | | // APP环境:使用uni.downloadFile |
| | | this.downloadFileInApp(url, fileName); |
| | | // #endif |
| | | |
| | | // #ifdef MP |
| | | // 小程序环境:使用uni.downloadFile |
| | | this.downloadFileInApp(url, fileName); |
| | | // #endif |
| | | }, |
| | | |
| | | // 在浏览器中下载文件 |
| | | downloadFileInBrowser(url, fileName) { |
| | | uni.showLoading({ title: '正在准备下载...' }); |
| | | |
| | | // 方法1:创建隐藏的a标签下载 |
| | | try { |
| | | 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); |
| | | |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '下载已开始', |
| | | icon: 'success', |
| | | duration: 2000 |
| | | }); |
| | | } catch (error) { |
| | | console.log('a标签下载失败,尝试window.open方式:', error); |
| | | // 方法2:使用window.open |
| | | try { |
| | | window.open(url, '_blank'); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '下载已开始', |
| | | icon: 'success', |
| | | duration: 2000 |
| | | }); |
| | | } catch (error2) { |
| | | console.log('window.open下载失败,尝试fetch方式:', error2); |
| | | // 方法3:使用fetch下载 |
| | | this.downloadFileWithFetch(url, fileName); |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // 使用fetch下载文件 |
| | | downloadFileWithFetch(url, fileName) { |
| | | fetch(url) |
| | | .then(response => { |
| | | if (!response.ok) { |
| | | throw new Error(`HTTP error! status: ${response.status}`); |
| | | } |
| | | return response.blob(); |
| | | }) |
| | | .then(blob => { |
| | | // 创建blob URL |
| | | const blobUrl = window.URL.createObjectURL(blob); |
| | | |
| | | // 创建下载链接 |
| | | const link = document.createElement('a'); |
| | | link.href = blobUrl; |
| | | link.download = fileName; |
| | | link.style.display = 'none'; |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | document.body.removeChild(link); |
| | | |
| | | // 释放blob URL |
| | | window.URL.revokeObjectURL(blobUrl); |
| | | |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '下载成功', |
| | | icon: 'success', |
| | | duration: 2000 |
| | | }); |
| | | }) |
| | | .catch(error => { |
| | | console.error('Fetch下载失败:', error); |
| | | uni.hideLoading(); |
| | | if (error.message.includes('404')) { |
| | | uni.showModal({ |
| | | title: '文件不存在', |
| | | content: `该附件在FTP服务器上不存在`, |
| | | showCancel: false |
| | | }); |
| | | } else { |
| | | uni.showModal({ |
| | | title: '下载失败', |
| | | content: `下载失败: ${error.message}`, |
| | | showCancel: false |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | // 在APP中下载文件 |
| | | downloadFileInApp(url, fileName) { |
| | | // #ifdef APP-PLUS |
| | | uni.showLoading({ title: '从FTP服务器下载中...' }); |
| | | |
| | | // Android 获取存储路径 |
| | | const saveDir = plus.os.name === 'Android' ? plus.io.convertLocalFileSystemURL('_downloads/') : plus.io.convertLocalFileSystemURL('_documents/'); |
| | | const filePath = `${saveDir}${fileName}`; |
| | | |
| | | const downloadTask = uni.downloadFile({ |
| | | url: url, |
| | | filePath: filePath, // 指定保存路径 |
| | | success: (res) => { |
| | | uni.hideLoading(); |
| | | if (res.statusCode === 200) { |
| | | const fileInfo = { |
| | | name: fileName, |
| | | path: res.filePath || filePath, |
| | | tempPath: res.tempFilePath |
| | | }; |
| | | |
| | | uni.showModal({ |
| | | title: '下载成功', |
| | | content: `文件已保存到:${fileInfo.path}`, |
| | | showCancel: true, |
| | | confirmText: '打开文件', |
| | | cancelText: '确定', |
| | | success: (modalRes) => { |
| | | if (modalRes.confirm) { |
| | | // 用户选择打开文件 |
| | | this.openFileInApp(fileInfo); |
| | | } |
| | | } |
| | | }); |
| | | } else if (res.statusCode === 404) { |
| | | uni.showModal({ |
| | | title: '文件不存在', |
| | | content: `该附件在FTP服务器上不存在`, |
| | | showCancel: false |
| | | }); |
| | | } else { |
| | | uni.showModal({ |
| | | title: '下载失败', |
| | | content: `状态码:${res.statusCode}`, |
| | | showCancel: false |
| | | }); |
| | | } |
| | | }, |
| | | fail: (error) => { |
| | | uni.hideLoading(); |
| | | console.error('下载失败:', error); |
| | | uni.showModal({ |
| | | title: '下载失败', |
| | | content: `网络错误:${error.errMsg}`, |
| | | showCancel: false |
| | | }); |
| | | } |
| | | }); |
| | | |
| | | // 监听下载进度 |
| | | downloadTask.onProgressUpdate((res) => { |
| | | const progress = Math.round(res.progress); |
| | | uni.showLoading({ |
| | | title: `下载中 ${progress}%`, |
| | | mask: true |
| | | }); |
| | | }); |
| | | // #endif |
| | | |
| | | // #ifdef MP |
| | | // 小程序环境的简化实现 |
| | | uni.showLoading({ title: '下载中...' }); |
| | | uni.downloadFile({ |
| | | url: url, |
| | | success: (res) => { |
| | | uni.hideLoading(); |
| | | if (res.statusCode === 200) { |
| | | uni.showToast({ title: '下载完成', icon: 'success' }); |
| | | } |
| | | }, |
| | | fail: (error) => { |
| | | uni.hideLoading(); |
| | | uni.showModal({ title: '下载失败', content: error.errMsg, showCancel: false }); |
| | | } |
| | | }); |
| | | // #endif |
| | | }, |
| | | |
| | | // APP中打开文件 |
| | | openFileInApp(fileInfo) { |
| | | // #ifdef APP-PLUS |
| | | if (typeof plus !== 'undefined') { |
| | | const filePath = fileInfo.path || fileInfo.tempPath; |
| | | |
| | | // 尝试打开文件 |
| | | plus.runtime.openFile(filePath, {}, (error) => { |
| | | console.error('打开文件失败:', error); |
| | | uni.showModal({ |
| | | title: '无法打开', |
| | | content: '系统中没有找到能打开此文件的应用程序', |
| | | showCancel: false |
| | | }); |
| | | }); |
| | | } |
| | | // #endif |
| | | }, |
| | | |
| | | } |
| | |
| | | font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif; |
| | | max-width: 1000px; |
| | | margin: 0 auto; |
| | | padding: 20px; |
| | | padding: 20px 20px 160px 20px; /* 底部增加padding为固定按钮留空间 */ |
| | | background-color: #fff; |
| | | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| | | min-height: 100vh; |
| | | position: relative; |
| | | } |
| | | |
| | | /* 头部样式 */ |
| | |
| | | background-color: #f1f5f9; |
| | | } |
| | | |
| | | /* 按钮样式 */ |
| | | /* 固定底部按钮样式 */ |
| | | .fixed-action-buttons { |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | background-color: #fff; |
| | | box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); |
| | | padding: 10px 15px 20px 15px; |
| | | z-index: 100; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | max-height: 150px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .action-btn { |
| | | background-color: #ecf0f1; |
| | | color: #34495e; |
| | | padding: 12px 15px; |
| | | border: none; |
| | | border-radius: 6px; |
| | | cursor: pointer; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | transition: all 0.3s ease; |
| | | text-align: center; |
| | | min-height: 44px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .action-btn:hover { |
| | | background-color: #d5dbdb; |
| | | transform: translateY(-1px); |
| | | } |
| | | |
| | | .action-btn.primary { |
| | | background-color: #3498db; |
| | | color: #fff; |
| | | } |
| | | |
| | | .action-btn.primary:hover { |
| | | background-color: #2980b9; |
| | | } |
| | | |
| | | /* 内容包装器,为底部按钮留出空间 */ |
| | | .content-wrapper { |
| | | height: 20px; /* 额外的空白区域 */ |
| | | } |
| | | |
| | | /* 原有按钮样式保持兼容 */ |
| | | .action-buttons { |
| | | display: flex; |
| | | gap: 10px; |
| | |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | z-index: 10; |
| | | z-index: 1000; /* 提高层级,确保在固定按钮上方 */ |
| | | } |
| | | |
| | | /* 弹窗整体美化 */ |
| | |
| | | border: none; |
| | | position: relative; |
| | | min-width: 260px; |
| | | z-index: 1001; /* 确保弹窗内容在最上层 */ |
| | | max-height: 80vh; /* 限制最大高度,避免被底部按钮遮挡 */ |
| | | overflow-y: auto; /* 内容过多时可滚动 */ |
| | | } |
| | | .attachment-popup-title { |
| | | font-size: 22px; |
| | |
| | | background: linear-gradient(90deg,#bdbdbd 0%,#e0e0e0 100%); |
| | | color: #1976d2; |
| | | } |
| | | |
| | | /* 附件详情页面的操作按钮 */ |
| | | .attachment-actions-detail { |
| | | margin: 20px 0; |
| | | display: flex; |
| | | gap: 12px; |
| | | justify-content: center; |
| | | flex-wrap: wrap; |
| | | } |
| | | .attachment-action-btn { |
| | | padding: 10px 20px; |
| | | border: none; |
| | | border-radius: 8px; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | min-width: 120px; |
| | | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | | } |
| | | .attachment-action-btn.preview-btn { |
| | | background: linear-gradient(135deg, #4CAF50, #45a049); |
| | | color: white; |
| | | } |
| | | .attachment-action-btn.preview-btn:hover { |
| | | background: linear-gradient(135deg, #45a049, #3d8b40); |
| | | transform: translateY(-1px); |
| | | box-shadow: 0 4px 8px rgba(0,0,0,0.15); |
| | | } |
| | | .attachment-action-btn.download-btn { |
| | | background: linear-gradient(135deg, #2196F3, #1976D2); |
| | | color: white; |
| | | } |
| | | .attachment-action-btn.download-btn:hover { |
| | | background: linear-gradient(135deg, #1976D2, #1565C0); |
| | | transform: translateY(-1px); |
| | | box-shadow: 0 4px 8px rgba(0,0,0,0.15); |
| | | } |
| | | |
| | | /* 文件预览弹窗样式 */ |
| | | .file-preview-popup { |
| | | width: 80vw; |
| | | max-width: 600px; |
| | | max-height: 70vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | /* APP环境适配 */ |
| | | /* #ifdef APP-PLUS */ |
| | | .file-preview-popup { |
| | | width: 85vw; |
| | | max-height: 75vh; |
| | | } |
| | | .file-preview-content { |
| | | max-height: 350px; |
| | | } |
| | | /* #endif */ |
| | | .file-preview-title { |
| | | font-size: 18px; |
| | | font-weight: 700; |
| | | color: #222; |
| | | margin-bottom: 8px; |
| | | text-align: center; |
| | | word-break: break-all; |
| | | } |
| | | .file-preview-divider { |
| | | height: 1px; |
| | | background: linear-gradient(90deg,#e0e7ef 0%,#f5f7fa 100%); |
| | | margin-bottom: 16px; |
| | | } |
| | | .file-preview-content { |
| | | flex: 1; |
| | | max-height: 400px; |
| | | overflow-y: auto; |
| | | background: #f8fafc; |
| | | border-radius: 8px; |
| | | padding: 16px; |
| | | margin-bottom: 16px; |
| | | border: 1px solid #e2e8f0; |
| | | } |
| | | .file-preview-content pre { |
| | | font-family: 'Consolas', 'Monaco', 'Courier New', monospace; |
| | | font-size: 12px; |
| | | line-height: 1.5; |
| | | color: #2d3748; |
| | | white-space: pre-wrap; |
| | | word-wrap: break-word; |
| | | margin: 0; |
| | | } |
| | | |
| | | /* 图片预览样式 */ |
| | | .image-preview-container { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | min-height: 200px; |
| | | } |
| | | |
| | | /* 不支持文件类型的提示样式 */ |
| | | .unsupported-preview { |
| | | text-align: center; |
| | | padding: 40px 20px; |
| | | color: #666; |
| | | } |
| | | .unsupported-icon { |
| | | font-size: 48px; |
| | | margin-bottom: 16px; |
| | | } |
| | | .unsupported-text { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | } |
| | | .unsupported-hint { |
| | | font-size: 14px; |
| | | color: #999; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .file-preview-actions { |
| | | display: flex; |
| | | gap: 12px; |
| | | justify-content: center; |
| | | } |
| | | .file-preview-btn { |
| | | padding: 8px 20px; |
| | | border: none; |
| | | border-radius: 6px; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | min-width: 120px; |
| | | } |
| | | .file-preview-btn.download-btn { |
| | | background: linear-gradient(135deg, #2196F3, #1976D2); |
| | | color: white; |
| | | } |
| | | .file-preview-btn.download-btn:hover { |
| | | background: linear-gradient(135deg, #1976D2, #1565C0); |
| | | transform: translateY(-1px); |
| | | } |
| | | .file-preview-btn.close-btn { |
| | | background: linear-gradient(135deg, #e0e0e0, #bdbdbd); |
| | | color: #444; |
| | | } |
| | | .file-preview-btn.close-btn:hover { |
| | | background: linear-gradient(135deg, #bdbdbd, #9e9e9e); |
| | | transform: translateY(-1px); |
| | | } |
| | | /* 列表弹窗美化(保留原有) */ |
| | | .attachment-list { |
| | | padding: 0; |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 8px 0; |
| | | padding: 12px 0; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | .attachment-name { |
| | | .attachment-info { |
| | | flex: 1; |
| | | margin-right: 10px; |
| | | } |
| | | .attachment-name { |
| | | color: #3498db; |
| | | cursor: pointer; |
| | | font-weight: 500; |
| | | transition: color 0.2s; |
| | | margin-right: 10px; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .attachment-name:hover { |
| | | color: #217dbb; |
| | | text-decoration: underline; |
| | | } |
| | | .attachment-meta { |
| | | font-size: 12px; |
| | | } |
| | | .attachment-type { |
| | | color: #7f8c8d; |
| | | font-style: italic; |
| | | } |
| | | .attachment-actions { |
| | | display: flex; |
| | | gap: 8px; |
| | | flex-shrink: 0; |
| | | } |
| | | .attachment-list .secondary-btn { |
| | | padding: 4px 10px; |
| | |
| | | background: #e6f0fa; |
| | | color: #1976d2; |
| | | } |
| | | .preview-btn { |
| | | background: #e8f5e8 !important; |
| | | color: #2e7d2e !important; |
| | | border-color: #a5d6a5 !important; |
| | | } |
| | | .preview-btn:hover { |
| | | background: #d4eecc !important; |
| | | color: #1e5f1e !important; |
| | | } |
| | | .attachment-popup-close { |
| | | margin-top: 18px; |
| | | width: 100%; |