| manifest.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/QC/RKJ/detail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/QC/SJ/Add.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/QC/SJ/detail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pages/QC/XJ/detail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| store/index.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
manifest.json
@@ -2,7 +2,7 @@ "name" : "GS-MES-AP", "appid" : "__UNI__F08FAE3", "description" : "", "versionName" : "1.1.3.8", "versionName" : "1.1.3.9", "versionCode" : 1, "transformPx" : false, /* 5+App特有相关 */ pages/QC/RKJ/detail.vue
@@ -162,6 +162,7 @@ <!-- 操作按钮 --> <view class="action-buttons"> <button class="action-btn warning" @click="saveRemarks">添加不合格描述</button> <button class="action-btn primary" @click="viewAttachmentInfo">查看检验项目</button> </view> <!-- 修改检验结果弹出框 --> @@ -201,6 +202,162 @@ </view> </view> </view> <!-- 附件列表弹窗 --> <view v-if="showAttachmentPopup" class="overlay"> <view class="popup attachment-list-popup"> <div class="attachment-popup-header"> <h3>附件列表</h3> <button class="close-btn" @click="closeAttachmentPopup">×</button> </div> <div class="attachment-popup-content"> <div v-if="attachmentsLoading" class="attachment-loading"> <div class="loading-spinner"></div> <span class="loading-text">正在加载附件...</span> </div> <div v-else-if="attachments.length === 0" class="attachment-empty"> <div class="empty-icon">📁</div> <div class="empty-text">暂无附件</div> </div> <div v-else class="attachment-list"> <div v-for="item in attachments" :key="item.id" class="attachment-item"> <div class="attachment-info"> <div class="file-type-badge" :class="getFileTypeClass(item.fattach)"> {{ getFileTypeIcon(item.fattach) }} </div> <div class="attachment-details"> <div class="attachment-name" @click="showAttachmentDetailDialog(item)"> {{ item.fattach }} </div> <div class="attachment-meta"> <span class="attachment-type">{{ item.ftype || '未知类型' }}</span> <span v-if="item.fversion" class="attachment-version">{{ item.fversion }}</span> </div> </div> </div> <div class="attachment-actions"> <button class="btn-secondary" @click="showAttachmentDetailDialog(item)">详情</button> <button v-if="isPreviewable(item.fattach)" class="btn-primary" @click="previewFtpFile(item)">预览</button> <button class="btn-success" @click="downloadAttachment(item)">下载</button> </div> </div> </div> </div> </view> </view> <!-- 附件详情弹窗 --> <view v-if="showAttachmentDetail" class="overlay"> <view class="popup attachment-detail-popup"> <div class="attachment-popup-header"> <h3>附件详情</h3> <button class="close-btn" @click="closeAttachmentDetail">×</button> </div> <div class="attachment-popup-content"> <div v-if="selectedAttachment" class="attachment-detail-content"> <div class="attachment-detail-header"> <div class="file-type-badge large" :class="getFileTypeClass(selectedAttachment.fattach)"> {{ getFileTypeIcon(selectedAttachment.fattach) }} </div> <div class="attachment-detail-title"> {{ selectedAttachment.fattach }} </div> </div> <div class="attachment-detail-info"> <div class="info-row"> <div class="info-item"> <text class="info-label">ID</text> <text class="info-content">{{ Math.trunc(selectedAttachment.id) }}</text> </div> <div class="info-item"> <text class="info-label">类型</text> <text class="info-content">{{ selectedAttachment.ftype || '未知类型' }}</text> </div> </div> <div class="info-row" v-if="selectedAttachment.fversion"> <div class="info-item"> <text class="info-label">版本</text> <text class="info-content">{{ selectedAttachment.fversion }}</text> </div> <div class="info-item" v-if="selectedAttachment.fdate"> <text class="info-label">受控日期</text> <text class="info-content">{{ formatDate(selectedAttachment.fdate) }}</text> </div> </div> <div class="info-row" v-if="selectedAttachment.createBy"> <div class="info-item"> <text class="info-label">上传人</text> <text class="info-content">{{ selectedAttachment.createBy }}</text> </div> <div class="info-item" v-if="selectedAttachment.createDate"> <text class="info-label">上传时间</text> <text class="info-content">{{ formatDate(selectedAttachment.createDate) }}</text> </div> </div> </div> <div class="attachment-detail-actions"> <button v-if="isPreviewable(selectedAttachment.fattach)" class="btn-primary" @click="previewFtpFile(selectedAttachment)">预览</button> <button class="btn-success" @click="downloadAttachment(selectedAttachment)">下载</button> </div> </div> </div> </view> </view> <!-- 文件预览弹窗 --> <view v-if="showFilePreviewPopup" class="overlay"> <view class="popup file-preview-popup"> <div class="attachment-popup-header"> <h3>{{ previewTitle }}</h3> <button class="close-btn" @click="closeFilePreview">×</button> </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="aspectFit" class="preview-image-clickable" @click="previewImageInPopup" style="width: 100%; max-height: 400px; cursor: pointer;" /> <div class="image-zoom-hint">点击图片可放大查看</div> </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> </template> @@ -223,7 +380,18 @@ base64Image: "", remarks: "", remarksPopup: false, fcheckResu: null fcheckResu: null, // 附件相关数据 showAttachmentPopup: false, showAttachmentDetail: false, showFilePreviewPopup: false, attachments: [], attachmentsLoading: false, selectedAttachment: null, previewTitle: '', previewContent: '', previewType: '', // 'text', 'image', 'excel', 'unsupported' previewFileUrl: '' } }, methods: { @@ -441,6 +609,266 @@ this.$showMessage("保存成功"); } }) } }, // 附件相关方法 viewAttachmentInfo() { this.showAttachmentPopup = true; this.attachmentsLoading = true; this.attachments = []; // 调试日志 console.log('viewAttachmentInfo 被调用'); console.log('formData.projName:', this.formData.projName); console.log('formData.itemNo:', this.formData.itemNo); console.log('formData:', this.formData); this.$post({ url: "/RKJ/getAttachments", data: { itemNo: this.formData.itemNo, projName: this.formData.projName, // 传递项目名称 fromPage: 'Detail' // 标识来自Detail页面,需要过滤 } }).then(res => { 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 { uni.showToast({ title: "获取附件失败", icon: "none" }); } }); }, closeAttachmentPopup() { this.showAttachmentPopup = false; }, showAttachmentDetailDialog(item) { this.selectedAttachment = item; this.showAttachmentPopup = false; this.showAttachmentDetail = true; }, closeAttachmentDetail() { this.showAttachmentDetail = false; this.selectedAttachment = null; this.showAttachmentPopup = true; }, isPreviewable(filename) { if (!filename) return false; const ext = filename.trim().split('.').pop().toLowerCase(); return [ 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'txt', 'log', 'md', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'csv' ].includes(ext); }, getFileTypeIcon(filename) { if (!filename) return '📄'; const ext = filename.trim().split('.').pop().toLowerCase(); const iconMap = { 'pdf': '📕', 'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️', 'bmp': '🖼️', 'webp': '🖼️', 'txt': '📝', 'log': '📝', 'md': '📝', 'doc': '📘', 'docx': '📘', 'xls': '📊', 'xlsx': '📊', 'ppt': '📙', 'pptx': '📙', 'csv': '📊', 'default': '📄' }; return iconMap[ext] || iconMap.default; }, getFileTypeClass(filename) { if (!filename) return 'file-unknown'; const ext = filename.trim().split('.').pop().toLowerCase(); const classMap = { 'pdf': 'file-pdf', 'jpg': 'file-image', 'jpeg': 'file-image', 'png': 'file-image', 'gif': 'file-image', 'bmp': 'file-image', 'webp': 'file-image', 'txt': 'file-text', 'log': 'file-text', 'md': 'file-text', 'doc': 'file-word', 'docx': 'file-word', 'xls': 'file-excel', 'xlsx': 'file-excel', 'ppt': 'file-powerpoint', 'pptx': 'file-powerpoint', 'csv': 'file-excel', 'default': 'file-unknown' }; return classMap[ext] || classMap.default; }, formatDate(dateString) { if (!dateString) return ''; try { const date = new Date(dateString); return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } catch (error) { return dateString; } }, downloadAttachment(item) { const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim(); const downloadUrl = this.$store.state.serverInfo.serverAPI + "/RKJ/DownloadFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer) + "&projName=" + encodeURIComponent(this.formData.projName || ''); uni.downloadFile({ url: downloadUrl, success: (res) => { if (res.statusCode === 200) { uni.showToast({ title: '下载成功', icon: 'success' }); } else { uni.showToast({ title: '下载失败', icon: 'none' }); } }, fail: (error) => { console.error('下载失败:', error); uni.showToast({ title: '下载失败', icon: 'none' }); } }); }, previewFtpFile(item) { const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim(); const fileExt = fileName.split('.').pop().toLowerCase(); if (!this.isPreviewable(fileName)) { uni.showModal({ title: '不支持预览', content: '该文件类型不支持在线预览,请下载后查看', showCancel: false }); return; } const previewUrl = this.$store.state.serverInfo.serverAPI + "/RKJ/PreviewFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer) + "&projName=" + encodeURIComponent(this.formData.projName || ''); 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); } }, previewPdfFile(url, fileName) { this.previewTitle = fileName; this.previewType = 'pdf'; this.previewFileUrl = url; this.showFilePreviewPopup = true; this.showAttachmentDetail = false; }, previewImageFile(url, fileName) { this.previewTitle = fileName; this.previewType = 'image'; this.previewContent = url; this.previewFileUrl = url; this.showFilePreviewPopup = true; this.showAttachmentDetail = false; }, previewTextFile(url, fileName) { this.previewTitle = fileName; this.previewType = 'text'; this.previewFileUrl = url; this.showFilePreviewPopup = true; this.showAttachmentDetail = false; uni.request({ url: url, method: 'GET', success: (res) => { if (res.statusCode === 200) { this.previewContent = res.data; } else { this.previewContent = '无法加载文件内容'; } }, fail: (error) => { this.previewContent = '加载文件失败'; } }); }, previewOfficeFile(url, fileName) { this.previewTitle = fileName; this.previewType = 'excel'; this.previewFileUrl = url; this.showFilePreviewPopup = true; this.showAttachmentDetail = false; }, previewGenericFile(url, fileName) { this.previewTitle = fileName; this.previewType = 'unsupported'; this.previewFileUrl = url; this.showFilePreviewPopup = true; this.showAttachmentDetail = false; }, closeFilePreview() { this.showFilePreviewPopup = false; this.showAttachmentDetail = true; }, downloadPreviewFile() { if (this.previewFileUrl && this.selectedAttachment) { this.downloadAttachment(this.selectedAttachment); } }, previewImageInPopup() { // 在新窗口中打开图片进行放大查看 if (this.previewContent) { uni.previewImage({ urls: [this.previewContent], current: this.previewContent }); } } }, @@ -799,6 +1227,396 @@ color: $danger-color; } /* 附件相关样式 */ .attachment-list-popup, .attachment-detail-popup, .file-preview-popup { width: 90vw; max-width: 600px; max-height: 80vh; } .attachment-popup-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid #eee; h3 { margin: 0; font-size: 18px; font-weight: 600; color: #2c3e50; } .close-btn { background: none; border: none; font-size: 24px; color: #999; cursor: pointer; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; &:hover { color: #333; } } } .attachment-popup-content { padding: 20px; max-height: 60vh; overflow-y: auto; } .attachment-loading { display: flex; flex-direction: column; align-items: center; padding: 40px 20px; .loading-spinner { width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 16px; } .loading-text { color: #666; font-size: 14px; } } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .attachment-empty { display: flex; flex-direction: column; align-items: center; padding: 40px 20px; .empty-icon { font-size: 48px; margin-bottom: 16px; opacity: 0.5; } .empty-text { color: #999; font-size: 16px; } } .attachment-list { .attachment-item { display: flex; align-items: center; justify-content: space-between; padding: 16px; border: 1px solid #eee; border-radius: 8px; margin-bottom: 12px; background: #fafafa; transition: all 0.2s; &:hover { background: #f0f0f0; border-color: #ddd; } } .attachment-info { display: flex; align-items: center; flex: 1; margin-right: 16px; } .file-type-badge { width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 20px; margin-right: 12px; flex-shrink: 0; &.file-pdf { background: #ffebee; color: #d32f2f; } &.file-image { background: #e8f5e8; color: #2e7d32; } &.file-text { background: #fff3e0; color: #f57c00; } &.file-word { background: #e3f2fd; color: #1976d2; } &.file-excel { background: #e8f5e8; color: #388e3c; } &.file-powerpoint { background: #fce4ec; color: #c2185b; } &.file-unknown { background: #f5f5f5; color: #757575; } } .attachment-details { flex: 1; min-width: 0; } .attachment-name { font-weight: 500; color: #333; margin-bottom: 4px; cursor: pointer; word-break: break-all; &:hover { color: #1976d2; text-decoration: underline; } } .attachment-meta { display: flex; gap: 12px; font-size: 12px; color: #666; } .attachment-actions { display: flex; gap: 8px; flex-shrink: 0; } } .attachment-detail-content { .attachment-detail-header { display: flex; align-items: center; margin-bottom: 24px; padding-bottom: 16px; border-bottom: 1px solid #eee; } .file-type-badge.large { width: 60px; height: 60px; font-size: 28px; margin-right: 16px; } .attachment-detail-title { font-size: 18px; font-weight: 600; color: #333; word-break: break-all; } .attachment-detail-info { margin-bottom: 24px; } .info-row { display: flex; margin-bottom: 12px; &:last-child { margin-bottom: 0; } } .info-item { flex: 1; margin-right: 16px; &:last-child { margin-right: 0; } } .info-label { display: block; font-size: 12px; color: #666; margin-bottom: 4px; } .info-content { font-size: 14px; color: #333; font-weight: 500; } .attachment-detail-actions { display: flex; gap: 12px; justify-content: center; } } .file-preview-content { max-height: 400px; overflow-y: auto; background: #f8f9fa; border-radius: 8px; padding: 16px; margin-bottom: 16px; 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; flex-direction: column; align-items: center; .preview-image-clickable { border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); cursor: pointer; transition: transform 0.2s; &:hover { transform: scale(1.02); } } .image-zoom-hint { margin-top: 8px; font-size: 12px; color: #666; font-style: italic; } } .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; &.download-btn { background: linear-gradient(135deg, #2196F3, #1976D2); color: white; &:hover { background: linear-gradient(135deg, #1976D2, #1565C0); transform: translateY(-1px); } } &.close-btn { background: linear-gradient(135deg, #e0e0e0, #bdbdbd); color: #444; &:hover { background: linear-gradient(135deg, #bdbdbd, #9e9e9e); transform: translateY(-1px); } } } .btn-secondary { background: #f5f7fa; color: #333; border: 1px solid #dbe2ea; padding: 6px 12px; border-radius: 4px; font-size: 12px; cursor: pointer; transition: all 0.2s; &:hover { background: #e6f0fa; color: #1976d2; } } .btn-primary { background: #e8f5e8; color: #2e7d2e; border: 1px solid #a5d6a5; padding: 6px 12px; border-radius: 4px; font-size: 12px; cursor: pointer; transition: all 0.2s; &:hover { background: #d4eecc; color: #1e5f1e; } } .btn-success { background: #e3f2fd; color: #1976d2; border: 1px solid #90caf9; padding: 6px 12px; border-radius: 4px; font-size: 12px; cursor: pointer; transition: all 0.2s; &:hover { background: #bbdefb; color: #1565c0; } } /* 响应式设计 */ @media (max-width: 768px) { .info-grid { @@ -820,5 +1638,36 @@ padding: 8px 4px; } } .attachment-list-popup, .attachment-detail-popup, .file-preview-popup { width: 95vw; max-height: 90vh; } .attachment-item { flex-direction: column; align-items: flex-start; .attachment-info { margin-right: 0; margin-bottom: 12px; width: 100%; } .attachment-actions { width: 100%; justify-content: flex-end; } } .attachment-detail-actions { flex-direction: column; } .file-preview-actions { flex-direction: column; } } </style> pages/QC/SJ/Add.vue
@@ -1266,7 +1266,10 @@ this.attachments = []; this.$post({ url: "/SJ/getAttachments", data: { itemNo: this.formData.itemNo } data: { itemNo: this.formData.itemNo, fromPage: 'Add' // 标识来自Add页面,不过滤 } }).then(res => { this.attachmentsLoading = false; if (res.status === 0) { pages/QC/SJ/detail.vue
@@ -895,10 +895,14 @@ if (tbBillListElement && tbBillListElement.itemNo) { console.log("获取到的物料编码:", tbBillListElement.itemNo); // 使用获取到的物料编码调用附件接口 // 使用获取到的物料编码和项目名称调用附件接口 return this.$post({ url: "/SJ/getAttachments", data: { itemNo: tbBillListElement.itemNo } data: { itemNo: tbBillListElement.itemNo, projName: this.formData.projName, // 传递项目名称 fromPage: 'Detail' // 标识来自Detail页面,需要过滤 } }); } else { throw new Error("未找到物料编码信息"); @@ -1011,7 +1015,8 @@ const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim(); const downloadUrl = this.$store.state.serverInfo.serverAPI + "/SJ/DownloadFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer); "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer) + "&projName=" + encodeURIComponent(this.formData.projName || ''); uni.downloadFile({ url: downloadUrl, @@ -1044,7 +1049,8 @@ const previewUrl = this.$store.state.serverInfo.serverAPI + "/SJ/PreviewFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer); "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer) + "&projName=" + encodeURIComponent(this.formData.projName || ''); if (['pdf'].includes(fileExt)) { this.previewPdfFile(previewUrl, fileName); pages/QC/XJ/detail.vue
@@ -140,6 +140,7 @@ <!-- 操作按钮 --> <view class="action-buttons"> <button class="action-btn warning" @click="saveRemarks">添加不合格描述</button> <button class="action-btn primary" @click="viewAttachmentInfo">查看附件信息</button> </view> <!-- 修改不合格描述弹出框 --> @@ -179,6 +180,168 @@ </view> </view> </view> <!-- 附件列表弹窗 --> <view v-if="showAttachmentPopup" class="overlay"> <view class="popup attachment-list-popup"> <div class="attachment-popup-header"> <h3 class="attachment-popup-title">附件列表</h3> <button class="attachment-close-btn" @click="closeAttachmentPopup">关闭</button> </div> <div class="attachment-popup-content"> <div v-if="attachmentsLoading" class="attachment-loading"> <div class="loading-spinner"></div> <span class="loading-text">正在加载附件...</span> </div> <div v-else-if="attachments.length === 0" class="attachment-empty"> <div class="empty-icon">📁</div> <div class="empty-text">暂无附件</div> <div class="empty-hint">该物料暂未上传任何附件</div> </div> <div v-else class="attachment-list"> <div v-for="item in attachments" :key="item.id" class="attachment-item"> <div class="attachment-info"> <div class="file-type-badge" :class="getFileTypeClass(item.fattach)"> {{ getFileTypeIcon(item.fattach) }} </div> <div class="attachment-details"> <div class="attachment-name" @click="showAttachmentDetailDialog(item)"> {{ item.fattach }} </div> <div class="attachment-meta"> <span class="meta-type">{{ item.ftype || '未知类型' }}</span> <span v-if="item.fversion" class="meta-version">v{{ item.fversion }}</span> <span v-if="item.fdate" class="meta-date">{{ formatDate(item.fdate) }}</span> </div> </div> </div> <div class="attachment-actions"> <button class="btn-secondary" @click="showAttachmentDetailDialog(item)">详情</button> <button v-if="isPreviewable(item.fattach)" class="btn-primary" @click="previewFtpFile(item)">预览</button> <button class="btn-success" @click="downloadAttachment(item)">下载</button> </div> </div> </div> </div> </view> </view> <!-- 附件详情弹窗 --> <view v-if="showAttachmentDetail" class="overlay"> <view class="popup attachment-detail-popup"> <div class="attachment-popup-header"> <h3 class="attachment-popup-title">附件详情</h3> <button class="attachment-close-btn" @click="closeAttachmentDetail">返回</button> </div> <div class="attachment-popup-content"> <div v-if="selectedAttachment" class="attachment-detail-content"> <div class="attachment-detail-header"> <div class="file-type-badge large" :class="getFileTypeClass(selectedAttachment.fattach)"> {{ getFileTypeIcon(selectedAttachment.fattach) }} </div> <div class="attachment-detail-title"> {{ selectedAttachment.fattach }} </div> </div> <div class="attachment-detail-info"> <div class="info-row"> <div class="info-item"> <text class="info-label">ID</text> <text class="info-content">{{ Math.trunc(selectedAttachment.id) }}</text> </div> <div class="info-item"> <text class="info-label">类型</text> <text class="info-content">{{ selectedAttachment.ftype || '未知类型' }}</text> </div> </div> <div class="info-row" v-if="selectedAttachment.fversion"> <div class="info-item"> <text class="info-label">版本</text> <text class="info-content">{{ selectedAttachment.fversion }}</text> </div> <div class="info-item" v-if="selectedAttachment.fdate"> <text class="info-label">受控日期</text> <text class="info-content">{{ formatDate(selectedAttachment.fdate) }}</text> </div> </div> <div class="info-row" v-if="selectedAttachment.createBy"> <div class="info-item"> <text class="info-label">上传人</text> <text class="info-content">{{ selectedAttachment.createBy }}</text> </div> <div class="info-item" v-if="selectedAttachment.createDate"> <text class="info-label">上传时间</text> <text class="info-content">{{ formatDate(selectedAttachment.createDate) }}</text> </div> </div> </div> <div class="attachment-detail-actions"> <button v-if="isPreviewable(selectedAttachment.fattach)" class="btn-primary" @click="previewFtpFile(selectedAttachment)">预览</button> <button class="btn-success" @click="downloadAttachment(selectedAttachment)">下载</button> </div> </div> <div v-else class="attachment-detail-empty"> <div class="empty-icon">❌</div> <div class="empty-text">暂无附件信息</div> </div> </div> </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="aspectFit" class="preview-image-clickable" @click="previewImageInPopup" style="width: 100%; max-height: 400px; cursor: pointer;" /> <div class="image-zoom-hint">点击图片可放大查看</div> </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> </template> @@ -199,6 +362,18 @@ base64Image:"", remarks: "", remarksPopup: false, // 附件相关数据 showAttachmentPopup: false, showAttachmentDetail: false, showFilePreviewPopup: false, attachments: [], attachmentsLoading: false, selectedAttachment: null, previewTitle: '', previewContent: '', previewType: '', previewFileUrl: '', } }, methods: { @@ -406,6 +581,243 @@ this.$showMessage("保存成功"); } }) } }, // 附件相关方法 viewAttachmentInfo() { this.showAttachmentPopup = true; this.attachmentsLoading = true; this.attachments = []; // 调试日志 console.log('viewAttachmentInfo 被调用'); console.log('formData.projName:', this.formData.projName); console.log('formData:', this.formData); this.$post({ url: "/XJ/getAttachments", data: { itemNo: this.formData.itemNo, projName: this.formData.projName, // 传递项目名称 fromPage: 'Detail' // 标识来自Detail页面,需要过滤 } }).then(res => { 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 { uni.showToast({ title: "获取附件失败", icon: "none" }); } }); }, closeAttachmentPopup() { this.showAttachmentPopup = false; }, showAttachmentDetailDialog(item) { this.selectedAttachment = item; this.showAttachmentPopup = false; this.showAttachmentDetail = true; }, closeAttachmentDetail() { this.showAttachmentDetail = false; this.selectedAttachment = null; this.showAttachmentPopup = true; }, isPreviewable(filename) { if (!filename) return false; const ext = filename.trim().split('.').pop().toLowerCase(); return [ 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'txt', 'log', 'md', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'csv' ].includes(ext); }, // 获取文件类型图标 getFileTypeIcon(filename) { if (!filename) return '📄'; const ext = filename.trim().split('.').pop().toLowerCase(); const iconMap = { 'pdf': '📕', 'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️', 'bmp': '🖼️', 'webp': '🖼️', 'txt': '📝', 'log': '📝', 'md': '📝', 'doc': '📘', 'docx': '📘', 'xls': '📊', 'xlsx': '📊', 'ppt': '📙', 'pptx': '📙', 'csv': '📊', 'zip': '📦', 'rar': '📦', '7z': '📦', 'dwg': '🏗️', 'dxf': '🏗️' }; return iconMap[ext] || '📄'; }, // 获取文件类型CSS类 getFileTypeClass(filename) { if (!filename) return 'file-unknown'; const ext = filename.trim().split('.').pop().toLowerCase(); const classMap = { 'pdf': 'file-pdf', 'jpg': 'file-image', 'jpeg': 'file-image', 'png': 'file-image', 'gif': 'file-image', 'bmp': 'file-image', 'webp': 'file-image', 'txt': 'file-text', 'log': 'file-text', 'md': 'file-text', 'doc': 'file-word', 'docx': 'file-word', 'xls': 'file-excel', 'xlsx': 'file-excel', 'ppt': 'file-powerpoint', 'pptx': 'file-powerpoint', 'csv': 'file-excel', 'zip': 'file-archive', 'rar': 'file-archive', '7z': 'file-archive', 'dwg': 'file-cad', 'dxf': 'file-cad' }; return classMap[ext] || 'file-unknown'; }, // 格式化日期 formatDate(dateString) { if (!dateString) return ''; try { const date = new Date(dateString); return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } catch (e) { return dateString; } }, downloadAttachment(item) { const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim(); const downloadUrl = this.$store.state.serverInfo.serverAPI + "/XJ/DownloadFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer) + "&projName=" + encodeURIComponent(this.formData.projName || ''); uni.downloadFile({ url: downloadUrl, success: (res) => { if (res.statusCode === 200) { uni.showToast({ title: '下载成功', icon: 'success' }); } else { uni.showToast({ title: '下载失败', icon: 'none' }); } }, fail: (err) => { console.error('下载失败:', err); uni.showToast({ title: '下载失败,请重试', icon: 'none' }); } }); }, previewFtpFile(item) { const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim(); const fileExt = fileName.split('.').pop().toLowerCase(); if (!this.isPreviewable(fileName)) { uni.showModal({ title: '不支持预览', content: '该文件类型不支持在线预览,请下载后查看', showCancel: false }); return; } const previewUrl = this.$store.state.serverInfo.serverAPI + "/XJ/PreviewFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer) + "&projName=" + encodeURIComponent(this.formData.projName || ''); 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 { this.previewOfficeFile(previewUrl, fileName); } }, previewPdfFile(url, fileName) { this.previewTitle = fileName; this.previewContent = url; this.previewType = 'pdf'; this.previewFileUrl = url; this.showFilePreviewPopup = true; this.showAttachmentDetail = false; }, previewImageFile(url, fileName) { this.previewTitle = fileName; this.previewContent = url; this.previewType = 'image'; this.previewFileUrl = url; this.showFilePreviewPopup = true; this.showAttachmentDetail = false; }, previewTextFile(url, fileName) { this.previewTitle = fileName; this.previewType = 'text'; this.previewFileUrl = url; this.showFilePreviewPopup = true; this.showAttachmentDetail = false; uni.request({ url: url, method: 'GET', success: (res) => { this.previewContent = res.data; }, fail: (err) => { this.previewContent = '预览失败,请下载后查看'; } }); }, previewOfficeFile(url, fileName) { this.previewTitle = fileName; this.previewContent = ''; this.previewType = 'excel'; this.previewFileUrl = url; this.showFilePreviewPopup = true; this.showAttachmentDetail = false; }, closeFilePreview() { this.showFilePreviewPopup = false; this.showAttachmentDetail = true; }, downloadPreviewFile() { if (this.previewFileUrl && this.selectedAttachment) { this.downloadAttachment(this.selectedAttachment); } }, previewImageInPopup() { // 在新窗口中打开图片进行放大查看 if (this.previewContent) { uni.previewImage({ urls: [this.previewContent], current: this.previewContent }); } } }, @@ -769,4 +1181,419 @@ margin-bottom: 8px; } } /* 附件相关样式 */ .attachment-list-popup { width: 80vw; max-width: 800px; max-height: 85vh; } .attachment-detail-popup { width: 70vw; max-width: 600px; } .attachment-popup-header { padding: 16px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; background-color: white; } .attachment-popup-title { font-size: 16px; font-weight: 600; color: #2c3e50; margin: 0; } .attachment-close-btn { padding: 8px 16px; border: 1px solid #ddd; border-radius: 4px; background-color: white; font-size: 14px; transition: all 0.2s; color: #2c3e50; } .attachment-close-btn:hover { background-color: #f8f9fa; } .attachment-popup-content { padding: 16px; max-height: 60vh; overflow-y: auto; } /* 加载状态 */ .attachment-loading { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; gap: 16px; } .loading-spinner { width: 32px; height: 32px; border: 3px solid #f3f3f3; border-top: 3px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .loading-text { font-size: 14px; color: #7f8c8d; } /* 空状态 */ .attachment-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; gap: 12px; text-align: center; } .empty-icon { font-size: 36px; opacity: 0.6; } .empty-text { font-size: 16px; color: #7f8c8d; font-weight: 500; } .empty-hint { font-size: 14px; color: #95a5a6; } /* 附件列表布局 */ .attachment-list { display: flex; flex-direction: column; gap: 16px; } .attachment-item { background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); overflow: hidden; transition: all 0.3s; border: 1px solid #eee; } .attachment-item:hover { box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); } .attachment-info { padding: 16px; border-bottom: 1px solid #eee; display: flex; align-items: center; gap: 16px; } .file-type-badge { width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 20px; background: #f8f9fa; border: 2px solid #e9ecef; flex-shrink: 0; } .file-type-badge.large { width: 56px; height: 56px; font-size: 28px; } .file-type-badge.file-pdf { background: #ffe6e6; border-color: #ffcccc; } .file-type-badge.file-image { background: #e6f3ff; border-color: #cce7ff; } .file-type-badge.file-text { background: #e6ffe6; border-color: #ccffcc; } .file-type-badge.file-word { background: #e6f0ff; border-color: #cce0ff; } .file-type-badge.file-excel { background: #e6ffe6; border-color: #ccffcc; } .file-type-badge.file-powerpoint { background: #fff0e6; border-color: #ffe0cc; } .file-type-badge.file-archive { background: #f0e6ff; border-color: #e0ccff; } .file-type-badge.file-cad { background: #e6fff0; border-color: #ccffe0; } .file-type-badge.file-unknown { background: #f5f5f5; border-color: #e0e0e0; } .attachment-details { flex: 1; min-width: 0; } .attachment-name { font-size: 16px; font-weight: 600; color: #2c3e50; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 8px; transition: color 0.2s; } .attachment-name:hover { color: #3498db; } .attachment-meta { display: flex; gap: 16px; font-size: 12px; color: #95a5a6; } .meta-type { background-color: #ecf0f1; padding: 2px 6px; border-radius: 10px; color: #7f8c8d; } .meta-version { background-color: #e8f5e8; padding: 2px 6px; border-radius: 10px; color: #2e7d32; } .meta-date { background-color: #fff3e0; padding: 2px 6px; border-radius: 10px; color: #f57c00; } .attachment-actions { padding: 12px 16px; border-top: 1px solid #eee; display: flex; gap: 8px; background-color: #f8f9fa; } /* 按钮样式 */ .btn-secondary { padding: 8px 16px; border: 1px solid #ddd; border-radius: 4px; background-color: white; font-size: 14px; transition: all 0.2s; color: #2c3e50; flex: 1; } .btn-secondary:hover { background-color: #f8f9fa; } .btn-primary { padding: 8px 16px; border: 1px solid #3498db; border-radius: 4px; background-color: #3498db; color: white; font-size: 14px; transition: all 0.2s; flex: 1; } .btn-primary:hover { background-color: #2980b9; } .btn-success { padding: 8px 16px; border: 1px solid #2ecc71; border-radius: 4px; background-color: #2ecc71; color: white; font-size: 14px; transition: all 0.2s; flex: 1; } .btn-success:hover { background-color: #27ae60; } /* 附件详情样式 */ .attachment-detail-header { display: flex; align-items: center; gap: 20px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid #eee; } .attachment-detail-title { font-size: 18px; font-weight: 600; color: #2c3e50; flex: 1; word-break: break-all; } .attachment-detail-info { margin-bottom: 20px; } .info-row { display: flex; margin-bottom: 12px; gap: 16px; } .info-item { flex: 1; } .info-label { display: block; font-size: 12px; color: #7f8c8d; margin-bottom: 4px; } .info-content { font-size: 14px; color: #2c3e50; line-height: 1.5; } .attachment-detail-actions { padding: 12px 16px; border-top: 1px solid #eee; display: flex; gap: 8px; background-color: #f8f9fa; } /* 文件预览弹窗样式 */ .file-preview-popup { width: 80vw; max-width: 800px; max-height: 80vh; } .file-preview-title { padding: 20px; margin: 0; font-size: 16px; font-weight: 600; color: #2c3e50; border-bottom: 1px solid #eee; word-break: break-all; } .file-preview-content { padding: 20px; max-height: 60vh; overflow-y: auto; } .file-preview-content pre { white-space: pre-wrap; word-wrap: break-word; font-family: 'Courier New', monospace; font-size: 12px; line-height: 1.4; background: #f8f9fa; padding: 15px; border-radius: 4px; border: 1px solid #e9ecef; } .image-preview-container { text-align: center; } .image-zoom-hint { margin-top: 10px; font-size: 12px; color: #7f8c8d; } .unsupported-preview { text-align: center; padding: 40px 20px; } .unsupported-icon { font-size: 48px; margin-bottom: 16px; } .unsupported-text { font-size: 16px; color: #7f8c8d; margin-bottom: 8px; } .unsupported-hint { font-size: 14px; color: #95a5a6; } .file-preview-actions { padding: 15px 20px; border-top: 1px solid #eee; display: flex; gap: 10px; justify-content: center; } .file-preview-btn { padding: 8px 16px; border-radius: 4px; border: none; font-size: 14px; cursor: pointer; transition: all 0.2s; } .file-preview-btn.download-btn { background: #2ecc71; color: white; } .file-preview-btn.close-btn { background: #95a5a6; color: white; } .file-preview-btn:hover { transform: translateY(-1px); box-shadow: 0 2px 8px rgba(0,0,0,0.15); } </style> store/index.js
@@ -12,7 +12,7 @@ serverURL:'http://localhost:10055',//本地调试地址 serverAPI:'http://localhost:5184/api',//当前正在使用的服务器,默认为外网 localhost //serverAPI:'http://192.168.1.22:10054/api',//内网 //serverAPI:'http://36.26.21.214:10054/api', //serverAPI:'http://36.26.21.214:10055/api', ftpServer:'ftp://36.26.21.214',//FTP服务器地址 } },