| src/pages.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/equipmentManagement/repair/add.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/equipmentManagement/repair/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/equipmentManagement/upkeep/add.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/equipmentManagement/upkeep/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/inspectionUpload/attachment.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/inspectionUpload/components/formDia.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/inspectionUpload/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/inspectionUpload/upload.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/pages.json
@@ -761,6 +761,20 @@ } }, { "path": "pages/inspectionUpload/upload", "style": { "navigationBarTitleText": "ä¸ä¼ å·¡æ£è®°å½", "navigationStyle": "custom" } }, { "path": "pages/inspectionUpload/attachment", "style": { "navigationBarTitleText": "æ¥çéä»¶", "navigationStyle": "custom" } }, { "path": "pages/equipmentManagement/faultAnalysis/index", "style": { "navigationBarTitleText": "æ éåæè¿½æº¯", src/pages/equipmentManagement/repair/add.vue
@@ -69,6 +69,20 @@ placeholder="请è¾å ¥æ¥ä¿®äºº" clearable /> </u-form-item> <u-form-item label="维修人" prop="maintenanceName" border-bottom> <u-input v-model="form.maintenanceName" placeholder="请è¾å ¥ç»´ä¿®äºº" clearable /> </u-form-item> <u-form-item label="维修项ç®" prop="maintenanceProject" border-bottom> <u-input v-model="form.maintenanceProject" placeholder="请è¾å ¥ç»´ä¿®é¡¹ç®" clearable /> </u-form-item> <u-form-item label="æ éç°è±¡" prop="remark" required @@ -169,6 +183,8 @@ deviceModel: undefined, // è§æ ¼åå· repairTime: dayjs().format("YYYY-MM-DD"), // æ¥ä¿®æ¥æ repairName: undefined, // æ¥ä¿®äºº maintenanceName: undefined, // 维修人 maintenanceProject: undefined, // ç»´ä¿®é¡¹ç® remark: undefined, // æ éç°è±¡ }); @@ -221,6 +237,8 @@ form.value.deviceModel = data.deviceModel; form.value.repairTime = dayjs(data.repairTime).format("YYYY-MM-DD"); form.value.repairName = data.repairName; form.value.maintenanceName = data.maintenanceName; form.value.maintenanceProject = data.maintenanceProject; form.value.remark = data.remark; repairStatusText.value = repairStatusOptions.value.find(item => item.value == data.status) src/pages/equipmentManagement/repair/index.vue
@@ -58,14 +58,18 @@ <text class="detail-value">{{ item.repairName || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">æ éç°è±¡</text> <text class="detail-value">{{ item.remark || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">维修人</text> <text class="detail-value">{{ item.maintenanceName || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">维修项ç®</text> <text class="detail-value">{{ item.maintenanceProject || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">æ éç°è±¡</text> <text class="detail-value">{{ item.remark || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">ç»´ä¿®ç»æ</text> <text class="detail-value">{{ item.maintenanceResult || '-' }}</text> </view> src/pages/equipmentManagement/upkeep/add.vue
@@ -41,6 +41,22 @@ </template> </u-form-item> <u-form-item label="ä¿å »äºº" prop="maintenancePerson" border-bottom> <u-input v-model="form.maintenancePerson" placeholder="请è¾å ¥ä¿å »äºº" clearable /> </u-form-item> <u-form-item label="ä¿å »é¡¹ç®" prop="maintenanceProject" border-bottom> <u-input v-model="form.maintenanceProject" placeholder="请è¾å ¥ä¿å »é¡¹ç®" clearable /> </u-form-item> <!-- æäº¤æé® --> <view class="footer-btns"> <u-button class="cancel-btn" @click="goBack">åæ¶</u-button> @@ -122,6 +138,8 @@ deviceLedgerId: undefined, // 设å¤ID deviceModel: undefined, // è§æ ¼åå· maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // 计åä¿å »æ¥æ maintenancePerson: undefined, // ä¿å »äºº maintenanceProject: undefined, // ä¿å »é¡¹ç® }); // å 载设å¤å表 @@ -144,6 +162,8 @@ form.value.deviceLedgerId = data.deviceLedgerId; form.value.deviceModel = data.deviceModel; form.value.maintenancePlanTime = dayjs(data.maintenancePlanTime).format("YYYY-MM-DD"); form.value.maintenancePerson = data.maintenancePerson; form.value.maintenanceProject = data.maintenanceProject; // 设置设å¤åç§°æ¾ç¤º const device = deviceOptions.value.find(item => item.id === data.deviceLedgerId); if (device) { src/pages/equipmentManagement/upkeep/index.vue
@@ -63,6 +63,14 @@ <text class="detail-value">{{ formatDateTime(item.createTime) || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">ä¿å »äºº</text> <text class="detail-value">{{ item.maintenancePerson || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">ä¿å »é¡¹ç®</text> <text class="detail-value">{{ item.maintenanceProject || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">å®é ä¿å »äºº</text> <text class="detail-value">{{ item.maintenanceActuallyName || '-' }}</text> </view> src/pages/inspectionUpload/attachment.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,460 @@ <template> <view class="attachment-page"> <!-- 页é¢å¤´é¨ --> <PageHeader :title="`æ¥çéä»¶ - ${taskInfo?.taskName || ''}`" @back="goBack" /> <!-- 页é¢å 容 --> <view class="attachment-content"> <!-- åç±»æ ç¾é¡µ --> <view class="attachment-tabs"> <view class="tab-item" :class="{ active: currentViewType === 'before' }" @click="switchViewType('before')" > ç产å ({{ getAttachmentsByType(0).length }}) </view> <view class="tab-item" :class="{ active: currentViewType === 'after' }" @click="switchViewType('after')" > çäº§ä¸ ({{ getAttachmentsByType(1).length }}) </view> <view class="tab-item" :class="{ active: currentViewType === 'issue' }" @click="switchViewType('issue')" > ç产å ({{ getAttachmentsByType(2).length }}) </view> </view> <!-- å½ååç±»çéä»¶å表 --> <view class="attachment-list-container"> <view v-if="getCurrentViewAttachments().length > 0" class="attachment-list"> <view v-for="(file, index) in getCurrentViewAttachments()" :key="index" class="attachment-item" @click="previewAttachment(file)" > <view class="attachment-preview-container"> <image v-if="isImageFile(file)" :src="file.url || file.downloadUrl" class="attachment-preview" mode="aspectFill" /> <view v-else class="attachment-video-preview"> <u-icon name="video" size="40" color="#409eff"></u-icon> <text class="video-text">è§é¢</text> </view> </view> <view class="attachment-info"> <text class="attachment-name">{{ file.originalFilename || file.bucketFilename || file.name || 'éä»¶' }}</text> <text class="attachment-size">{{ formatFileSize(file.byteSize || file.size) }}</text> </view> </view> </view> <view v-else class="attachment-empty"> <u-icon name="folder-open" size="60" color="#ccc"></u-icon> <text class="empty-text">该åç±»ææ éä»¶</text> </view> </view> </view> <!-- è§é¢é¢è§å¼¹çª --> <view v-if="showVideoDialog" class="video-modal-overlay" @click="closeVideoPreview"> <view class="video-modal-container" @click.stop> <view class="video-modal-header"> <text class="video-modal-title">{{ currentVideoFile?.originalFilename || 'è§é¢é¢è§' }}</text> <view class="close-btn-video" @click="closeVideoPreview"> <u-icon name="close" size="20" color="#fff"></u-icon> </view> </view> <view class="video-modal-body"> <video v-if="currentVideoFile" :src="currentVideoFile.url || currentVideoFile.downloadUrl" class="video-player" controls autoplay @error="handleVideoError" ></video> </view> </view> </view> </view> </template> <script setup> import { ref } from 'vue'; import { onLoad } from '@dcloudio/uni-app'; import PageHeader from '@/components/PageHeader.vue'; import config from '@/config'; // ä»»å¡ä¿¡æ¯ const taskInfo = ref(null); // éä»¶å表 const attachmentList = ref([]); // å½åæ¥çç±»å const currentViewType = ref('before'); // 'before', 'after', 'issue' // è§é¢é¢è§ç¸å ³ç¶æ const showVideoDialog = ref(false); const currentVideoFile = ref(null); // æä»¶è®¿é®åºç¡å const filePreviewBase = config.fileUrl; // 页é¢å è½½ onLoad((options) => { if (options.taskInfo) { try { taskInfo.value = JSON.parse(decodeURIComponent(options.taskInfo)); loadAttachments(); } catch (e) { console.error('è§£æä»»å¡ä¿¡æ¯å¤±è´¥:', e); uni.showToast({ title: 'å 载失败', icon: 'error' }); } } }); // å è½½éä»¶æ°æ® const loadAttachments = () => { const task = taskInfo.value; if (!task) return; attachmentList.value = []; // åç«¯åæ¾åæ®µ const allList = Array.isArray(task?.commonFileList) ? task.commonFileList : []; const beforeList = Array.isArray(task?.commonFileListBefore) ? task.commonFileListBefore : allList.filter((f) => f?.type === 10); const afterList = Array.isArray(task?.commonFileListAfter) ? task.commonFileListAfter : allList.filter((f) => f?.type === 11); const issueList = Array.isArray(task?.commonFileListIssue) ? task.commonFileListIssue : allList.filter((f) => f?.type === 12); const mapToViewFile = (file, viewType) => { const u = normalizeFileUrl(file?.url || file?.downloadUrl || ''); return { ...file, type: viewType, name: file?.name || file?.originalFilename || file?.bucketFilename, bucketFilename: file?.bucketFilename || file?.name, originalFilename: file?.originalFilename || file?.name, url: u, downloadUrl: u, size: file?.size || file?.byteSize, }; }; attachmentList.value.push(...beforeList.map((f) => mapToViewFile(f, 0))); attachmentList.value.push(...afterList.map((f) => mapToViewFile(f, 1))); attachmentList.value.push(...issueList.map((f) => mapToViewFile(f, 2))); }; // å°å端è¿åçæä»¶å°åè§èæå¯è®¿é®URL const normalizeFileUrl = (rawUrl) => { try { if (!rawUrl || typeof rawUrl !== 'string') return ''; const url = rawUrl.trim(); if (!url) return ''; if (/^https?:\/\//i.test(url)) return url; if (url.startsWith('/')) return `${filePreviewBase}${url}`; // Windows path -> web path if (/^[a-zA-Z]:\\/.test(url)) { const normalized = url.replace(/\\/g, '/'); const idx = normalized.indexOf('/prod/'); if (idx >= 0) { const relative = normalized.slice(idx + '/prod/'.length); return `${filePreviewBase}/${relative}`; } return normalized; } return `${filePreviewBase}/${url.replace(/^\//, '')}`; } catch (e) { return rawUrl || ''; } }; // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; // 忢æ¥çç±»å const switchViewType = (type) => { currentViewType.value = type; }; // æ ¹æ®typeè·å对åºåç±»çéä»¶ const getAttachmentsByType = (typeValue) => { return attachmentList.value.filter((file) => file.type === typeValue) || []; }; // è·åå½åæ¥çç±»åçéä»¶ const getCurrentViewAttachments = () => { switch (currentViewType.value) { case 'before': return getAttachmentsByType(0); case 'after': return getAttachmentsByType(1); case 'issue': return getAttachmentsByType(2); default: return []; } }; // 夿æ¯å¦ä¸ºå¾çæä»¶ const isImageFile = (file) => { if (file.contentType && file.contentType.startsWith('image/')) { return true; } if (file.type === 'image') return true; const name = file.bucketFilename || file.originalFilename || file.name || ''; const ext = name.split('.').pop()?.toLowerCase(); return ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext); }; // é¢è§éä»¶ const previewAttachment = (file) => { if (isImageFile(file)) { const imageUrls = getCurrentViewAttachments() .filter((f) => isImageFile(f)) .map((f) => f.url || f.downloadUrl); uni.previewImage({ urls: imageUrls, current: file.url || file.downloadUrl, }); } else { showVideoPreview(file); } }; // æ¾ç¤ºè§é¢é¢è§ const showVideoPreview = (file) => { currentVideoFile.value = file; showVideoDialog.value = true; }; // å ³éè§é¢é¢è§ const closeVideoPreview = () => { showVideoDialog.value = false; currentVideoFile.value = null; }; // è§é¢ææ¾é误å¤ç const handleVideoError = () => { uni.showToast({ title: 'è§é¢ææ¾å¤±è´¥', icon: 'error', }); }; // æ ¼å¼åæä»¶å¤§å° const formatFileSize = (size) => { if (!size) return ''; if (size < 1024) return size + 'B'; if (size < 1024 * 1024) return (size / 1024).toFixed(1) + 'KB'; return (size / (1024 * 1024)).toFixed(1) + 'MB'; }; </script> <style scoped> .attachment-page { min-height: 100vh; background-color: #f5f5f5; } .attachment-content { padding: 15px; } /* æ ç¾é¡µæ ·å¼ */ .attachment-tabs { display: flex; background: #fff; border-radius: 12px; margin-bottom: 15px; padding: 4px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .tab-item { flex: 1; text-align: center; padding: 12px 8px; font-size: 14px; color: #666; border-radius: 8px; transition: all 0.3s ease; } .tab-item.active { background: #409eff; color: #fff; font-weight: 500; } /* éä»¶åè¡¨æ ·å¼ */ .attachment-list-container { background: #fff; border-radius: 12px; padding: 15px; min-height: 400px; } .attachment-list { display: flex; flex-wrap: wrap; gap: 15px; } .attachment-item { width: calc(33.33% - 10px); background: #f8f9fa; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); transition: all 0.3s ease; } .attachment-item:active { transform: scale(0.98); } .attachment-preview-container { width: 100%; height: 120px; background: #e9ecef; display: flex; align-items: center; justify-content: center; } .attachment-preview { width: 100%; height: 100%; object-fit: cover; } .attachment-video-preview { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; } .video-text { font-size: 12px; color: #666; } .attachment-info { padding: 10px; } .attachment-name { font-size: 12px; color: #333; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; } .attachment-size { font-size: 10px; color: #999; } /* ç©ºç¶ææ ·å¼ */ .attachment-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 80px 20px; color: #999; } .empty-text { margin-top: 15px; font-size: 14px; } /* è§é¢å¼¹çªæ ·å¼ */ .video-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.9); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 20px; } .video-modal-container { width: 100%; max-width: 800px; background: #000; border-radius: 12px; overflow: hidden; } .video-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: #1a1a1a; } .video-modal-title { font-size: 16px; color: #fff; font-weight: 500; } .close-btn-video { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.1); border-radius: 50%; } .video-modal-body { padding: 20px; } .video-player { width: 100%; height: 400px; border-radius: 8px; } </style> src/pages/inspectionUpload/components/formDia.vue
@@ -8,10 +8,49 @@ > <view class="popup-content"> <view class="popup-header"> <text class="popup-title">ä¸ä¼ </text> <text class="popup-title">å·¡æ£è®°å½ä¸ä¼ </text> </view> <view class="upload-container"> <!-- å¼å¸¸ç¶æéæ© --> <view class="form-container"> <view class="title">å·¡æ£ç¶æ</view> <view class="exception-section"> <view class="exception-options"> <view class="exception-option" :class="{ active: hasException === false }" @click="setExceptionStatus(false)" > <u-icon name="checkmark-circle" size="20" color="#52c41a"></u-icon> <text class="option-text">æ£å¸¸</text> </view> <view class="exception-option" :class="{ active: hasException === true }" @click="setExceptionStatus(true)" > <u-icon name="close-circle" size="20" color="#ff4d4f"></u-icon> <text class="option-text">åå¨å¼å¸¸</text> </view> </view> </view> </view> <!-- å¼å¸¸æè¿°ï¼ä» å¨å¼å¸¸æ¶æ¾ç¤ºï¼ --> <view class="form-container" v-if="hasException === true"> <view class="title">å¼å¸¸æè¿°</view> <u-input v-model="exceptionDescription" type="textarea" :maxlength="500" placeholder="请æè¿°å¼å¸¸æ åµ..." :customStyle="{ padding: '10px', backgroundColor: '#f5f5f5' }" /> </view> <!-- ä¸ä¼ åºåï¼ä» å¨å¼å¸¸æ¶æ¾ç¤ºï¼ --> <template v-if="hasException === true"> <view class="form-container"> <view class="title">ç产å</view> <u-upload @@ -56,6 +95,13 @@ :previewFullImage="true" ></u-upload> </view> </template> <!-- æ£å¸¸ç¶ææç¤º --> <view class="form-container normal-tip" v-if="hasException === false"> <u-icon name="info-circle" size="40" color="#52c41a"></u-icon> <text class="tip-text">设å¤è¿è¡æ£å¸¸ï¼æ éä¸ä¼ ç §ç</text> </view> </view> <view class="popup-footer"> @@ -79,6 +125,11 @@ const afterModelValue = ref([]) const issueModelValue = ref([]) const infoData = ref(null) // å¼å¸¸ç¶æï¼null=æªéæ©, false=æ£å¸¸, true=å¼å¸¸ const hasException = ref(null) // å¼å¸¸æè¿° const exceptionDescription = ref('') // 计ç®ä¸ä¼ URL const uploadFileUrl = computed(() => { @@ -196,9 +247,43 @@ } } // 设置å¼å¸¸ç¶æ const setExceptionStatus = (status) => { hasException.value = status } // æäº¤è¡¨å const submitForm = async () => { try { // æ£æ¥æ¯å¦éæ©äºå·¡æ£ç¶æ if (hasException.value === null) { uni.showToast({ title: 'è¯·éæ©å·¡æ£ç¶æ', icon: 'none' }) return } // 妿æ¯å¼å¸¸ç¶æï¼æ£æ¥æ¯å¦æä¸ä¼ æä»¶ if (hasException.value === true) { const totalFiles = beforeModelValue.value.length + afterModelValue.value.length + issueModelValue.value.length if (totalFiles === 0) { uni.showToast({ title: '请ä¸ä¼ å¼å¸¸ç §ç', icon: 'none' }) return } // æ£æ¥æ¯å¦å¡«åäºå¼å¸¸æè¿° if (!exceptionDescription.value.trim()) { uni.showToast({ title: '请填åå¼å¸¸æè¿°', icon: 'none' }) return } } let arr = [] if (beforeModelValue.value.length > 0) { arr.push(...beforeModelValue.value.map(item => ({ ...item, statusType: 0 }))) @@ -212,6 +297,8 @@ // æäº¤æ°æ® infoData.value.storageBlobDTO = arr infoData.value.hasException = hasException.value infoData.value.exceptionDescription = exceptionDescription.value await submitInspectionRecord({ ...infoData.value }) uni.showToast({ @@ -238,6 +325,8 @@ beforeModelValue.value = [] afterModelValue.value = [] issueModelValue.value = [] hasException.value = null exceptionDescription.value = '' } // å ³éå¼¹æ¡ @@ -311,4 +400,61 @@ border-top: 1px solid #f0f0f0; background-color: #fafafa; } // å¼å¸¸ç¶æéæ©æ ·å¼ .exception-section { padding: 10px 0; } .exception-options { display: flex; gap: 15px; } .exception-option { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 15px 20px; border: 2px solid #e0e0e0; border-radius: 8px; cursor: pointer; transition: all 0.3s; background-color: #fff; &.active { border-color: #1890ff; background-color: #e6f7ff; } &:active { opacity: 0.8; } } .option-text { font-size: 14px; color: #333; font-weight: 500; } // æ£å¸¸ç¶ææç¤ºæ ·å¼ .normal-tip { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; background-color: #f6ffed; border: 1px dashed #b7eb8f; border-radius: 8px; .tip-text { margin-top: 15px; font-size: 14px; color: #52c41a; } } </style> src/pages/inspectionUpload/index.vue
@@ -56,6 +56,10 @@ <text class="detail-value">{{ item.taskId || item.id }}</text> </view> <view class="detail-item"> <text class="detail-label">å·¡æ£é¡¹ç®</text> <text class="detail-value">{{ item.inspectionProject || 'æ ' }}</text> </view> <view class="detail-item"> <text class="detail-label">夿³¨</text> <text class="detail-value">{{ item.remarks || 'æ ' }}</text> </view> @@ -95,227 +99,6 @@ <view v-if="taskTableData?.length === 0" class="no-data"> <text>ææ æ°æ®</text> </view> </view> <!-- å¾çä¸ä¼ å¼¹çª - åçå®ç° --> <view v-if="showUploadDialog" class="custom-modal-overlay" @click="closeUploadDialog"> <view class="custom-modal-container" @click.stop> <view class="upload-popup-content"> <view class="upload-popup-header"> <text class="upload-popup-title">ä¸ä¼ å·¡æ£è®°å½</text> </view> <view class="upload-popup-body"> <!-- åç±»æ ç¾é¡µ --> <view class="upload-tabs"> <view class="tab-item" :class="{ active: currentUploadType === 'before' }" @click="switchUploadType('before')"> ç产å </view> <view class="tab-item" :class="{ active: currentUploadType === 'after' }" @click="switchUploadType('after')"> çäº§ä¸ </view> <view class="tab-item" :class="{ active: currentUploadType === 'issue' }" @click="switchUploadType('issue')"> ç产å </view> </view> <!-- å¼å¸¸ç¶æéæ© --> <view class="exception-section"> <text class="section-title">æ¯å¦åå¨å¼å¸¸ï¼</text> <view class="exception-options"> <view class="exception-option" :class="{ active: hasException === false }" @click="setExceptionStatus(false)"> <u-icon name="checkmark-circle" size="20" color="#52c41a"></u-icon> <text>æ£å¸¸</text> </view> <view class="exception-option" :class="{ active: hasException === true }" @click="setExceptionStatus(true)"> <u-icon name="close-circle" size="20" color="#ff4d4f"></u-icon> <text>åå¨å¼å¸¸</text> </view> </view> </view> <!-- å½ååç±»çä¸ä¼ åºå --> <view class="simple-upload-area"> <view class="upload-buttons"> <u-button type="primary" @click="chooseMedia('image')" :loading="uploading" :disabled="getCurrentFiles().length >= uploadConfig.limit" :customStyle="{ marginRight: '10px', flex: 1 }"> <u-icon name="camera" size="18" color="#fff" style="margin-right: 5px;"></u-icon> {{ uploading ? 'ä¸ä¼ ä¸...' : 'æç §' }} </u-button> <u-button type="success" @click="chooseMedia('video')" :loading="uploading" :disabled="getCurrentFiles().length >= uploadConfig.limit" :customStyle="{ flex: 1 }"> <uni-icons type="videocam" name="videocam" size="18" color="#fff" style="margin-right: 5px;"></uni-icons> {{ uploading ? 'ä¸ä¼ ä¸...' : 'æè§é¢' }} </u-button> </view> <!-- ä¸ä¼ è¿åº¦ --> <view v-if="uploading" class="upload-progress"> <u-line-progress :percentage="uploadProgress" :showText="true" activeColor="#409eff"></u-line-progress> </view> <!-- å½ååç±»çæä»¶å表 --> <view v-if="getCurrentFiles().length > 0" class="file-list"> <view v-for="(file, index) in getCurrentFiles()" :key="index" class="file-item"> <view class="file-preview-container"> <image v-if="file.type === 'image' || (file.type !== 'video' && !file.type)" :src="file.url || file.tempFilePath || file.path || file.downloadUrl" class="file-preview" mode="aspectFill" /> <view v-else-if="file.type === 'video'" class="video-preview"> <uni-icons type="videocam" name="videocam" size="18" color="#fff" style="margin-right: 5px;"></uni-icons> <text class="video-text">è§é¢</text> </view> <!-- å é¤æé® --> <view class="delete-btn" @click="removeFile(index)"> <u-icon name="close" size="12" color="#fff"></u-icon> </view> </view> <view class="file-info"> <text class="file-name">{{ file.bucketFilename || file.name || (file.type === 'image' ? 'å¾ç' : 'è§é¢') }}</text> <text class="file-size">{{ formatFileSize(file.size) }}</text> </view> </view> </view> <view v-if="getCurrentFiles().length === 0" class="empty-state"> <text>è¯·éæ©è¦ä¸ä¼ ç{{ getUploadTypeText() }}å¾çæè§é¢</text> </view> <!-- ç»è®¡ä¿¡æ¯ --> <view class="upload-summary"> <text class="summary-text"> ç产å: {{ beforeModelValue.length }}个æä»¶ | ç产ä¸: {{ afterModelValue.length }}个æä»¶ | ç产å: {{ issueModelValue.length }}个æä»¶ </text> </view> </view> </view> <view class="upload-popup-footer"> <u-button @click="closeUploadDialog" :customStyle="{ marginRight: '10px' }">åæ¶</u-button> <u-button v-if="hasException === true" type="warning" @click="goToRepair" :customStyle="{ marginRight: '10px' }"> æ°å¢æ¥ä¿® </u-button> <u-button type="primary" @click="submitUpload">æäº¤</u-button> </view> </view> </view> </view> <!-- æ¥çéä»¶å¼¹çª --> <view v-if="showAttachmentDialog" class="custom-modal-overlay" @click="closeAttachmentDialog"> <view class="custom-modal-container" @click.stop> <view class="attachment-popup-content"> <view class="attachment-popup-header"> <text class="attachment-popup-title">æ¥çéä»¶ - {{ currentViewTask?.taskName }}</text> <view class="close-btn-attachment" @click="closeAttachmentDialog"> <u-icon name="close" size="16" color="#666"></u-icon> </view> </view> <view class="attachment-popup-body"> <!-- åç±»æ ç¾é¡µ --> <view class="attachment-tabs"> <view class="tab-item" :class="{ active: currentViewType === 'before' }" @click="switchViewType('before')"> ç产å ({{ getAttachmentsByType(0).length }}) </view> <view class="tab-item" :class="{ active: currentViewType === 'after' }" @click="switchViewType('after')"> çäº§ä¸ ({{ getAttachmentsByType(1).length }}) </view> <view class="tab-item" :class="{ active: currentViewType === 'issue' }" @click="switchViewType('issue')"> ç产å ({{ getAttachmentsByType(2).length }}) </view> </view> <!-- å½ååç±»çéä»¶å表 --> <view class="attachment-content"> <view v-if="getCurrentViewAttachments().length > 0" class="attachment-list"> <view v-for="(file, index) in getCurrentViewAttachments()" :key="index" class="attachment-item" @click="previewAttachment(file)"> <view class="attachment-preview-container"> <image v-if="file.type === 'image' || isImageFile(file)" :src="file.url || file.downloadUrl" class="attachment-preview" mode="aspectFill" /> <view v-else class="attachment-video-preview"> <u-icon name="video" size="24" color="#409eff"></u-icon> <text class="video-text">è§é¢</text> </view> </view> <view class="attachment-info"> <text class="attachment-name">{{ file.originalFilename || file.bucketFilename || file.name || 'éä»¶' }}</text> <text class="attachment-size">{{ formatFileSize(file.byteSize || file.size) }}</text> </view> </view> </view> <view v-else class="attachment-empty"> <text>该åç±»ææ éä»¶</text> </view> </view> </view> </view> </view> </view> <!-- è§é¢é¢è§å¼¹çª --> @@ -378,57 +161,9 @@ const currentScanningTask = ref(null); const infoData = ref(null); // ä¸ä¼ ç¸å ³ç¶æ const showUploadDialog = ref(false); const uploadFiles = ref([]); // ä¿çç¨äºå ¼å®¹æ§ const uploadStatusType = ref(0); const uploading = ref(false); const uploadProgress = ref(0); const number = ref(0); const uploadList = ref([]); // ä¸ä¸ªåç±»çä¸ä¼ ç¶æ const beforeModelValue = ref([]); // ç产å const afterModelValue = ref([]); // çäº§ä¸ const issueModelValue = ref([]); // ç产å // å½åæ¿æ´»çä¸ä¼ ç±»å const currentUploadType = ref("before"); // 'before', 'after', 'issue' // æ¥çéä»¶ç¸å ³ç¶æ const showAttachmentDialog = ref(false); const currentViewTask = ref(null); const currentViewType = ref("before"); // 'before', 'after', 'issue' const attachmentList = ref([]); // å½åæ¥çä»»å¡çéä»¶å表 // è§é¢é¢è§ç¸å ³ç¶æ const showVideoDialog = ref(false); const currentVideoFile = ref(null); // å¼å¸¸ç¶æ const hasException = ref(null); // null: æªéæ©, true: åå¨å¼å¸¸, false: æ£å¸¸ // ä¸ä¼ é ç½® const uploadConfig = { action: "/file/upload", limit: 10, fileSize: 50, // MB fileType: ["jpg", "jpeg", "png", "mp4", "mov"], maxVideoDuration: 60, // ç§ }; // 计ç®ä¸ä¼ URL const uploadFileUrl = computed(() => { const baseUrl = config.baseUrl; return baseUrl + uploadConfig.action; }); // 计ç®è¯·æ±å¤´ const headers = computed(() => { const token = getToken(); return token ? { Authorization: "Bearer " + token } : {}; }); // 请æ±åæ¶æ å¿ï¼ç¨äºåæ¶æ£å¨è¿è¡çè¯·æ± let isRequestCancelled = false; @@ -489,11 +224,6 @@ onUnmounted(() => { // è®¾ç½®åæ¶æ å¿ï¼é»æ¢åç»ç弿¥æä½ isRequestCancelled = true; // å ³éä¸ä¼ å¼¹çª if (showUploadDialog.value) { showUploadDialog.value = false; } }); // è¿åä¸ä¸é¡µ @@ -653,322 +383,27 @@ } }; // æå¼ä¸ä¼ å¼¹çª // æå¼ä¸ä¼ é¡µé¢ const openUploadDialog = task => { // 设置任å¡ä¿¡æ¯å°infoData if (task) { infoData.value = { ...task, taskId: task.taskId || task.id, storageBlobDTO: [], // åå§åæä»¶å表 }; } // 设置ä¸ä¼ ç¶æç±»åï¼å¯ä»¥æ ¹æ®ä»»å¡ç±»å设置ä¸åçç¶æï¼ uploadStatusType.value = 0; // é»è®¤ç¶æ // æ¸ ç©ºä¹åçæä»¶ uploadFiles.value = []; // æ¾ç¤ºä¸ä¼ å¼¹çª showUploadDialog.value = true; }; // å ³éä¸ä¼ å¼¹çª const closeUploadDialog = () => { showUploadDialog.value = false; uploadFiles.value = []; // æ¸ çä¸ä¸ªåç±»çæ°æ® beforeModelValue.value = []; afterModelValue.value = []; issueModelValue.value = []; currentUploadType.value = "before"; hasException.value = null; // éç½®å¼å¸¸ç¶æ infoData.value = null; // æ¸ ç任塿°æ® }; // 忢ä¸ä¼ ç±»å const switchUploadType = type => { currentUploadType.value = type; }; // è·åå½ååç±»çæä»¶å表 const getCurrentFiles = () => { switch (currentUploadType.value) { case "before": return beforeModelValue.value || []; case "after": return afterModelValue.value || []; case "issue": return issueModelValue.value || []; default: return []; } }; // è·åä¸ä¼ ç±»åææ¬ const getUploadTypeText = () => { switch (currentUploadType.value) { case "before": return "ç产å"; case "after": return "ç产ä¸"; case "issue": return "ç产å"; default: return ""; } }; // å¤çä¸ä¼ æä»¶æ´æ° const handleUploadUpdate = files => { uploadFiles.value = files; }; // 设置å¼å¸¸ç¶æ const setExceptionStatus = status => { hasException.value = status; }; // è·³è½¬å°æ°å¢æ¥ä¿®é¡µé¢ const goToRepair = () => { try { // åå¨å½åä»»å¡ä¿¡æ¯å°æ¬å°åå¨ï¼ä¾æ¥ä¿®é¡µé¢ä½¿ç¨ const taskInfo = { taskId: infoData.value?.taskId || infoData.value?.id, taskName: infoData.value?.taskName, inspectionLocation: infoData.value?.inspectionLocation, inspector: infoData.value?.inspector, // ä¼ éå½åä¸ä¼ çæä»¶ä¿¡æ¯ uploadedFiles: { before: beforeModelValue.value, after: afterModelValue.value, issue: issueModelValue.value, }, }; uni.setStorageSync("repairTaskInfo", JSON.stringify(taskInfo)); // è·³è½¬å°æ°å¢æ¥ä¿®é¡µé¢ // å°ä»»å¡ä¿¡æ¯ä¼ éå°ä¸ä¼ é¡µé¢ const taskData = encodeURIComponent(JSON.stringify(task)); uni.navigateTo({ url: "/pages/equipmentManagement/repair/add", url: `/pages/inspectionUpload/upload?taskInfo=${taskData}`, }); // å ³éä¸ä¼ å¼¹çª closeUploadDialog(); } catch (error) { console.error("跳转æ¥ä¿®é¡µé¢å¤±è´¥:", error); uni.showToast({ title: "跳转失败ï¼è¯·éè¯", icon: "error", }); } }; // æäº¤ä¸ä¼ const submitUpload = async () => { try { // æ£æ¥æ¯å¦éæ©äºå¼å¸¸ç¶æ if (hasException.value === null) { uni.showToast({ title: "è¯·éæ©æ¯å¦åå¨å¼å¸¸", icon: "none", }); return; } // æ£æ¥æ¯å¦æä»»ä½æä»¶ä¸ä¼ const totalFiles = beforeModelValue.value.length + afterModelValue.value.length + issueModelValue.value.length; if (totalFiles === 0) { uni.showToast({ title: "请å ä¸ä¼ æä»¶", icon: "none", }); return; } // æ¾ç¤ºæäº¤ä¸çå è½½æç¤º showLoadingToast("æäº¤ä¸..."); // æç §æ¨çé»è¾åå¹¶ææåç±»çæä»¶ let arr = []; if (beforeModelValue.value.length > 0) { arr.push(...beforeModelValue.value); } if (afterModelValue.value.length > 0) { arr.push(...afterModelValue.value); } if (issueModelValue.value.length > 0) { arr.push(...issueModelValue.value); } // ä¼ ç»å端çä¸´æ¶æä»¶IDå表ï¼tempFileIdsï¼ // å ¼å®¹ï¼æäºæ¥å£å¯è½è¿å tempId / tempFileId / id let tempFileIds = []; if (arr !== null && arr.length > 0) { tempFileIds = arr .map(item => item?.tempId ?? item?.tempFileId ?? item?.id) .filter(v => v !== undefined && v !== null && v !== ""); } // æäº¤æ°æ® infoData.value.storageBlobDTO = arr; // æ·»å å¼å¸¸ç¶æä¿¡æ¯ infoData.value.hasException = hasException.value; infoData.value.tempFileIds = tempFileIds; const result = await uploadInspectionTask({ ...infoData.value }); // æ£æ¥æäº¤ç»æ if (result && (result.code === 200 || result.success)) { // æäº¤æå closeToast(); // å ³éå è½½æç¤º uni.showToast({ title: "æäº¤æå", icon: "success", }); // å ³éå¼¹çª closeUploadDialog(); // å·æ°å表 setTimeout(() => { reloadPage(); }, 500); } else { // æäº¤å¤±è´¥ closeToast(); uni.showToast({ title: result?.msg || result?.message || "æäº¤å¤±è´¥", icon: "error", }); } } catch (error) { console.error("æäº¤ä¸ä¼ 失败:", error); closeToast(); // å ³éå è½½æç¤º let errorMessage = "æäº¤å¤±è´¥"; if (error.message) { errorMessage = error.message; } else if (error.msg) { errorMessage = error.msg; } else if (typeof error === "string") { errorMessage = error; } uni.showToast({ title: errorMessage, icon: "error", }); } }; // å¾çä¸ä¼ (å¯éæ©å¾çä¸ä¼ æè æ¯ç¸æºæç §) const startUploadForTask = async (task, type) => { // ç´æ¥æå¼ä¸ä¼ å¼¹çª // æå¼ä¸ä¼ é¡µé¢ openUploadDialog(task); }; // æ¥çéä»¶ // æ¥çéä»¶ - 跳转å°éä»¶é¡µé¢ const viewAttachments = async task => { try { currentViewTask.value = task; currentViewType.value = "before"; // è§£ææ°çæ°æ®ç»æ attachmentList.value = []; // åç«¯åæ¾åæ®µï¼ä½ æä¾çæ°æ®ç»æï¼ï¼ // - commonFileListBeforeï¼ç产åï¼é常 type=10ï¼ // - commonFileListAfterï¼ç产ä¸ï¼é常 type=11ï¼ // - commonFileListï¼å¯è½æ¯å ¨é¨/å åºï¼è¥å å«ç产åï¼ä¸è¬ type=12ï¼ const allList = Array.isArray(task?.commonFileList) ? task.commonFileList : []; const beforeList = Array.isArray(task?.commonFileListBefore) ? task.commonFileListBefore : allList.filter(f => f?.type === 10); const afterList = Array.isArray(task?.commonFileListAfter) ? task.commonFileListAfter : allList.filter(f => f?.type === 11); // 妿å端åç»è¡¥äº commonFileListIssueï¼åä¼å ç¨ï¼å¦åä» commonFileList éæ type=12 å åº const issueList = Array.isArray(task?.commonFileListIssue) ? task.commonFileListIssue : allList.filter(f => f?.type === 12); const mapToViewFile = (file, viewType) => { const u = normalizeFileUrl(file?.url || file?.downloadUrl || ""); return { ...file, // ç¨äºä¸æ ç¾é¡µåç»ï¼0=ç产å 1=çäº§ä¸ 2=ç产å type: viewType, name: file?.name || file?.originalFilename || file?.bucketFilename, bucketFilename: file?.bucketFilename || file?.name, originalFilename: file?.originalFilename || file?.name, url: u, downloadUrl: u, size: file?.size || file?.byteSize, }; }; attachmentList.value.push(...beforeList.map(f => mapToViewFile(f, 0))); attachmentList.value.push(...afterList.map(f => mapToViewFile(f, 1))); attachmentList.value.push(...issueList.map(f => mapToViewFile(f, 2))); showAttachmentDialog.value = true; } catch (error) { uni.showToast({ title: "è·åé件失败", icon: "error", const taskData = encodeURIComponent(JSON.stringify(task)); uni.navigateTo({ url: `/pages/inspectionUpload/attachment?taskInfo=${taskData}`, }); } }; // å ³ééä»¶æ¥çå¼¹çª const closeAttachmentDialog = () => { showAttachmentDialog.value = false; currentViewTask.value = null; attachmentList.value = []; currentViewType.value = "before"; }; // 忢æ¥çç±»å const switchViewType = type => { currentViewType.value = type; }; // æ ¹æ®typeè·å对åºåç±»çéä»¶ const getAttachmentsByType = typeValue => { return attachmentList.value.filter(file => file.type === typeValue) || []; }; // è·åtypeå¼ const getTabType = () => { switch (currentUploadType.value) { case "before": return 10; case "after": return 11; case "issue": return 12; default: return 10; } }; // è·åå½åæ¥çç±»åçéä»¶ const getCurrentViewAttachments = () => { switch (currentViewType.value) { case "before": return getAttachmentsByType(0); case "after": return getAttachmentsByType(1); case "issue": return getAttachmentsByType(2); default: return []; } }; // 夿æ¯å¦ä¸ºå¾çæä»¶ @@ -1060,474 +495,7 @@ }); }; // æç §/æè§é¢ï¼çæºä¼å ç¨ chooseMediaï¼ä¸æ¯æåéçº§ï¼ const chooseMedia = type => { if (getCurrentFiles().length >= uploadConfig.limit) { uni.showToast({ title: `æå¤åªè½éæ©${uploadConfig.limit}个æä»¶`, icon: "none", }); return; } const remaining = uploadConfig.limit - getCurrentFiles().length; // ä¼å ï¼chooseMediaï¼æ¯æ image/videoï¼ if (typeof uni.chooseMedia === "function") { uni.chooseMedia({ count: Math.min(remaining, 1), mediaType: [type || "image"], sizeType: ["compressed", "original"], sourceType: ["camera"], success: res => { try { const files = res?.tempFiles || []; if (!files.length) throw new Error("æªè·åå°æä»¶"); files.forEach((tf, idx) => { const filePath = tf.tempFilePath || tf.path || ""; const fileType = tf.fileType || type || "image"; const ext = fileType === "video" ? "mp4" : "jpg"; const file = { tempFilePath: filePath, path: filePath, type: fileType, name: `${fileType}_${Date.now()}_${idx}.${ext}`, size: tf.size || 0, duration: tf.duration || 0, createTime: Date.now(), uid: Date.now() + Math.random() + idx, }; handleBeforeUpload(file); }); } catch (e) { console.error("å¤çææç»æå¤±è´¥:", e); uni.showToast({ title: "å¤çæä»¶å¤±è´¥", icon: "error" }); } }, fail: err => { console.error("ææå¤±è´¥:", err); uni.showToast({ title: "ææå¤±è´¥", icon: "error" }); }, }); return; } // é级ï¼chooseImage / chooseVideo if (type === "video") { chooseVideo(); } else { uni.chooseImage({ count: 1, sizeType: ["compressed", "original"], sourceType: ["camera"], success: res => { const tempFilePath = res?.tempFilePaths?.[0]; const tempFile = res?.tempFiles?.[0] || {}; if (!tempFilePath) return; handleBeforeUpload({ tempFilePath, path: tempFilePath, type: "image", name: `photo_${Date.now()}.jpg`, size: tempFile.size || 0, createTime: Date.now(), uid: Date.now() + Math.random(), }); }, }); } }; // æç § const chooseImage = () => { if (uploadFiles.value.length >= uploadConfig.limit) { uni.showToast({ title: `æå¤åªè½ææ${uploadConfig.limit}个æä»¶`, icon: "none", }); return; } uni.chooseMedia({ count: 1, mediaType: ["image", "video"], sizeType: ["compressed", "original"], sourceType: ["camera"], success: res => { try { if (!res.tempFiles || res.tempFiles.length === 0) { throw new Error("æªè·åå°å¾çæä»¶"); } const tempFilePath = res.tempFiles[0]; const tempFile = res.tempFiles && res.tempFiles[0] ? res.tempFiles[0] : {}; const file = { tempFilePath: tempFilePath, path: tempFilePath, // ä¿æå ¼å®¹æ§ type: "image", name: `photo_${Date.now()}.jpg`, size: tempFile.size || 0, createTime: new Date().getTime(), uid: Date.now() + Math.random(), }; handleBeforeUpload(file); } catch (error) { console.error("å¤çæç §ç»æå¤±è´¥:", error); uni.showToast({ title: "å¤çå¾ç失败", icon: "error", }); } }, fail: err => { console.error("æç §å¤±è´¥:", err); uni.showToast({ title: "æç §å¤±è´¥: " + (err.errMsg || "æªç¥é误"), icon: "error", }); }, }); }; // æè§é¢ const chooseVideo = () => { if (uploadFiles.value.length >= uploadConfig.limit) { uni.showToast({ title: `æå¤åªè½ææ${uploadConfig.limit}个æä»¶`, icon: "none", }); return; } uni.chooseVideo({ sourceType: ["camera"], maxDuration: uploadConfig.maxVideoDuration, camera: "back", success: res => { try { if (!res.tempFilePath) { throw new Error("æªè·åå°è§é¢æä»¶"); } const file = { tempFilePath: res.tempFilePath, path: res.tempFilePath, // ä¿æå ¼å®¹æ§ type: "video", name: `video_${Date.now()}.mp4`, size: res.size || 0, duration: res.duration || 0, createTime: new Date().getTime(), uid: Date.now() + Math.random(), }; handleBeforeUpload(file); } catch (error) { console.error("å¤çæè§é¢ç»æå¤±è´¥:", error); uni.showToast({ title: "å¤çè§é¢å¤±è´¥", icon: "error", }); } }, fail: err => { console.error("æè§é¢å¤±è´¥:", err); uni.showToast({ title: "æè§é¢å¤±è´¥: " + (err.errMsg || "æªç¥é误"), icon: "error", }); }, }); }; // å 餿件 const removeFile = index => { uni.showModal({ title: "确认å é¤", content: "ç¡®å®è¦å é¤è¿ä¸ªæä»¶åï¼", success: res => { if (res.confirm) { // æ ¹æ®å½åä¸ä¼ ç±»åå é¤å¯¹åºåç±»çæä»¶ switch (currentUploadType.value) { case "before": beforeModelValue.value.splice(index, 1); break; case "after": afterModelValue.value.splice(index, 1); break; case "issue": issueModelValue.value.splice(index, 1); break; } uni.showToast({ title: "å 餿å", icon: "success", }); } }, }); }; // æ£æ¥ç½ç»è¿æ¥ const checkNetworkConnection = () => { return new Promise(resolve => { uni.getNetworkType({ success: res => { if (res.networkType === "none") { resolve(false); } else { resolve(true); } }, fail: () => { resolve(false); }, }); }); }; // ä¸ä¼ åæ ¡éª const handleBeforeUpload = async file => { // æ ¡éªæä»¶ç±»å if ( uploadConfig.fileType && Array.isArray(uploadConfig.fileType) && uploadConfig.fileType.length > 0 ) { const fileName = file.name || ""; const fileExtension = fileName ? fileName.split(".").pop().toLowerCase() : ""; // æ ¹æ®æä»¶ç±»åç¡®å®ææçæ©å±å let expectedTypes = []; if (file.type === "image") { expectedTypes = ["jpg", "jpeg", "png", "gif", "webp"]; } else if (file.type === "video") { expectedTypes = ["mp4", "mov", "avi", "wmv"]; } // æ£æ¥æä»¶æ©å±åæ¯å¦å¨å 许çç±»åä¸ if (fileExtension && expectedTypes.length > 0) { const isAllowed = expectedTypes.some( type => uploadConfig.fileType.includes(type) && type === fileExtension ); if (!isAllowed) { uni.showToast({ title: `æä»¶æ ¼å¼ä¸æ¯æï¼è¯·ææ ${expectedTypes.join("/")} æ ¼å¼çæä»¶`, icon: "none", }); return false; } } } // æ ¡éªéè¿ï¼å¼å§ä¸ä¼ uploadFile(file); return true; }; // æä»¶ä¸ä¼ å¤çï¼çæºèµ° uni.uploadFileï¼ const uploadFile = async file => { uploading.value = true; uploadProgress.value = 0; number.value++; // å¢å ä¸ä¼ è®¡æ° // ç¡®ä¿tokenåå¨ const token = getToken(); if (!token) { handleUploadError("ç¨æ·æªç»å½"); return; } const typeValue = getTabType(); // ç产å:10, ç产ä¸:11, ç产å:12 uploadWithUniUploadFile( file, file.tempFilePath || file.path || "", typeValue, token ); }; // 使ç¨uni.uploadFileä¸ä¼ ï¼éH5ç¯å¢æH5åéæ¹æ¡ï¼ const uploadWithUniUploadFile = (file, filePath, typeValue, token) => { if (!filePath) { handleUploadError("æä»¶è·¯å¾ä¸åå¨"); return; } const uploadTask = uni.uploadFile({ url: uploadFileUrl.value, filePath: filePath, name: "file", formData: { type: typeValue, }, header: { Authorization: `Bearer ${token}`, }, success: res => { try { if (res.statusCode === 200) { const response = JSON.parse(res.data); if (response.code === 200) { handleUploadSuccess(response, file); uni.showToast({ title: "ä¸ä¼ æå", icon: "success", }); } else { handleUploadError(response.msg || "æå¡å¨è¿åé误"); } } else { handleUploadError(`æå¡å¨é误ï¼ç¶æç : ${res.statusCode}`); } } catch (e) { console.error("è§£æååºå¤±è´¥:", e); console.error("åå§ååºæ°æ®:", res.data); handleUploadError("ååºæ°æ®è§£æå¤±è´¥: " + e.message); } }, fail: err => { console.error("ä¸ä¼ 失败:", err.errMsg || err); number.value--; // ä¸ä¼ 失败æ¶åå°è®¡æ° let errorMessage = "ä¸ä¼ 失败"; if (err.errMsg) { if (err.errMsg.includes("statusCode: null")) { errorMessage = "ç½ç»è¿æ¥å¤±è´¥ï¼è¯·æ£æ¥ç½ç»è®¾ç½®"; } else if (err.errMsg.includes("timeout")) { errorMessage = "ä¸ä¼ è¶ æ¶ï¼è¯·éè¯"; } else if (err.errMsg.includes("fail")) { errorMessage = "ä¸ä¼ 失败ï¼è¯·æ£æ¥ç½ç»è¿æ¥"; } else { errorMessage = err.errMsg; } } handleUploadError(errorMessage); }, complete: () => { uploading.value = false; uploadProgress.value = 0; }, }); // çå¬ä¸ä¼ è¿åº¦ if (uploadTask && uploadTask.onProgressUpdate) { uploadTask.onProgressUpdate(res => { uploadProgress.value = res.progress; }); } }; // ä¸ä¼ 失败å¤ç const handleUploadError = (message = "ä¸ä¼ æä»¶å¤±è´¥", showRetry = false) => { uploading.value = false; uploadProgress.value = 0; if (showRetry) { uni.showModal({ title: "ä¸ä¼ 失败", content: message + "ï¼æ¯å¦éè¯ï¼", success: res => { if (res.confirm) { // ç¨æ·éæ©éè¯ï¼è¿éå¯ä»¥éæ°è§¦åä¸ä¼ } }, }); } else { uni.showToast({ title: message, icon: "error", }); } }; // ä¸ä¼ æååè° const handleUploadSuccess = (res, file) => { console.log("ä¸ä¼ æåååº:", res); // å¤çä¸åçæ°æ®ç»æï¼å¯è½æ¯æ°ç»ï¼ä¹å¯è½æ¯å个对象 let uploadedFile = null; uploadedFile = res.data; if (!uploadedFile) { console.error("æ æ³è§£æä¸ä¼ ååºæ°æ®:", res); number.value--; // ä¸ä¼ 失败æ¶åå°è®¡æ° handleUploadError("ä¸ä¼ ååºæ°æ®æ ¼å¼é误", false); return; } // æ ¹æ®å½åä¸ä¼ ç±»å设置typeåæ®µ let typeValue = 0; // é»è®¤ä¸ºç产å switch (currentUploadType.value) { case "before": typeValue = 0; break; case "after": typeValue = 1; break; case "issue": typeValue = 2; break; } // ç¡®ä¿ä¸ä¼ çæä»¶æ°æ®å®æ´ï¼å å«idåtype const fileData = { ...file, id: uploadedFile.id, // æ·»å æå¡å¨è¿åçid tempId: uploadedFile.tempId ?? uploadedFile.tempFileId ?? uploadedFile.id, url: uploadedFile.url || uploadedFile.downloadUrl || file.tempFilePath || file.path, bucketFilename: uploadedFile.bucketFilename || uploadedFile.originalFilename || file.name, downloadUrl: uploadedFile.downloadUrl || uploadedFile.url, size: uploadedFile.size || uploadedFile.byteSize || file.size, createTime: uploadedFile.createTime || new Date().getTime(), type: typeValue, // æ·»å ç±»ååæ®µï¼0=ç产å, 1=ç产ä¸, 2=ç产å }; uploadList.value.push(fileData); // ç«å³æ·»å å°å¯¹åºçåç±»ï¼ä¸çå¾ æææä»¶ä¸ä¼ 宿 switch (currentUploadType.value) { case "before": beforeModelValue.value.push(fileData); break; case "after": afterModelValue.value.push(fileData); break; case "issue": issueModelValue.value.push(fileData); break; } // éç½®ä¸ä¼ å表ï¼å ä¸ºå·²ç»æ·»å å°å¯¹åºåç±»äºï¼ uploadList.value = []; number.value = 0; }; // ä¸ä¼ ç»æå¤çï¼å·²åºå¼ï¼ç°å¨å¨handleUploadSuccessä¸ç´æ¥å¤çï¼ const uploadedSuccessfully = () => { // æ¤å½æ°å·²ä¸å使ç¨ï¼æä»¶ä¸ä¼ æååç«å³æ·»å å°å¯¹åºåç±» }; // æ ¼å¼åæä»¶å¤§å° const formatFileSize = size => { if (!size) return ""; if (size < 1024) return size + "B"; if (size < 1024 * 1024) return (size / 1024).toFixed(1) + "KB"; return (size / (1024 * 1024)).toFixed(1) + "MB"; }; </script> <style scoped> @@ -1725,416 +693,6 @@ display: flex; align-items: center; justify-content: center; } /* ä¸ä¼ å¼¹çªæ ·å¼ */ .upload-popup-content { background: #fff; border-radius: 12px; width: 100%; min-height: 300px; max-height: 70vh; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } .upload-popup-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; border-bottom: 1px solid #eee; } .upload-popup-title { font-size: 16px; font-weight: 600; color: #333; } .upload-popup-body { flex: 1; padding: 20px; overflow-y: auto; } .upload-popup-footer { display: flex; justify-content: flex-end; padding: 15px 20px; border-top: 1px solid #eee; gap: 10px; } /* ç®åä¸ä¼ ç»ä»¶æ ·å¼ */ .simple-upload-area { padding: 15px; } .upload-buttons { display: flex; gap: 10px; margin-bottom: 15px; } .file-list { margin-top: 15px; display: flex; flex-wrap: wrap; gap: 12px; } .file-item { display: flex; flex-direction: column; align-items: center; background: #fff; border-radius: 12px; padding: 8px; border: 1px solid #e9ecef; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); transition: all 0.3s ease; width: calc(50% - 6px); min-width: 120px; } .file-item:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); } .file-preview-container { position: relative; margin-bottom: 8px; } .file-preview { width: 80px; height: 80px; border-radius: 8px; object-fit: cover; border: 2px solid #f0f0f0; } .video-preview { width: 80px; height: 80px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; border: 2px solid #f0f0f0; } .video-text { font-size: 12px; color: #666; margin-top: 4px; } .delete-btn { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; background: #ff4757; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 4px rgba(255, 71, 87, 0.3); transition: all 0.3s ease; } .delete-btn:hover { background: #ff3742; transform: scale(1.1); } .file-info { text-align: center; width: 100%; } .file-name { font-size: 12px; color: #333; font-weight: 500; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100px; } .file-size { font-size: 10px; color: #999; margin-top: 2px; display: block; } .empty-state { text-align: center; padding: 40px 20px; color: #999; font-size: 14px; background: #f8f9fa; border-radius: 8px; border: 2px dashed #ddd; } .upload-progress { margin: 15px 0; padding: 0 10px; } /* ä¸ä¼ æ ç¾é¡µæ ·å¼ */ .upload-tabs { display: flex; background: #f8f9fa; border-radius: 8px; margin-bottom: 15px; padding: 4px; } .tab-item { flex: 1; text-align: center; padding: 8px 12px; font-size: 14px; color: #666; border-radius: 6px; transition: all 0.3s ease; cursor: pointer; } .tab-item.active { background: #409eff; color: #fff; font-weight: 500; } .tab-item:hover:not(.active) { background: #e9ecef; color: #333; } /* å¼å¸¸ç¶æéæ©æ ·å¼ */ .exception-section { margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e9ecef; } .section-title { display: block; font-size: 14px; font-weight: 600; color: #333; margin-bottom: 12px; } .exception-options { display: flex; gap: 12px; } .exception-option { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 16px; background: #fff; border: 2px solid #e9ecef; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; font-size: 14px; color: #666; } .exception-option.active { border-color: #409eff; background: #f0f8ff; color: #409eff; font-weight: 500; } .exception-option:hover:not(.active) { border-color: #d9d9d9; background: #fafafa; } /* ç»è®¡ä¿¡æ¯æ ·å¼ */ .upload-summary { margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 6px; border-left: 3px solid #409eff; } .summary-text { font-size: 12px; color: #666; line-height: 1.4; } /* æ¥çéä»¶å¼¹çªæ ·å¼ */ .attachment-popup-content { background: #fff; border-radius: 12px; width: 100%; min-height: 400px; max-height: 70vh; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } .attachment-popup-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; border-bottom: 1px solid #eee; background: #f8f9fa; } .attachment-popup-title { font-size: 16px; font-weight: 600; color: #333; } .close-btn-attachment { width: 28px; height: 28px; border-radius: 50%; background: #f5f5f5; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; } .close-btn-attachment:hover { background: #e9ecef; transform: scale(1.1); } .attachment-popup-body { flex: 1; padding: 15px 20px; overflow-y: auto; } .attachment-tabs { display: flex; background: #f8f9fa; border-radius: 8px; margin-bottom: 15px; padding: 4px; } .attachment-content { min-height: 200px; } .attachment-list { display: flex; flex-wrap: wrap; gap: 12px; } .attachment-item { display: flex; flex-direction: column; align-items: center; background: #fff; border-radius: 12px; padding: 8px; border: 1px solid #e9ecef; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); transition: all 0.3s ease; width: calc(33.33% - 8px); min-width: 100px; cursor: pointer; } .attachment-item:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); } .attachment-preview-container { margin-bottom: 8px; } .attachment-preview { width: 80px; height: 80px; border-radius: 8px; object-fit: cover; border: 2px solid #f0f0f0; } .attachment-video-preview { width: 80px; height: 80px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; border: 2px solid #f0f0f0; } .attachment-info { text-align: center; width: 100%; } .attachment-name { font-size: 12px; color: #333; font-weight: 500; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 80px; } .attachment-size { font-size: 10px; color: #999; margin-top: 2px; display: block; } .attachment-empty { text-align: center; padding: 60px 20px; color: #999; font-size: 14px; background: #f8f9fa; border-radius: 8px; border: 2px dashed #ddd; } /* è§é¢é¢è§å¼¹çªæ ·å¼ */ src/pages/inspectionUpload/upload.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,885 @@ <template> <view class="inspection-upload-page"> <!-- 页é¢å¤´é¨ --> <PageHeader title="ä¸ä¼ å·¡æ£è®°å½" @back="goBack" /> <!-- 页é¢å 容 --> <view class="upload-content"> <!-- ä»»å¡ä¿¡æ¯å¡ç --> <view class="task-info-card" v-if="taskInfo"> <view class="task-info-header"> <text class="task-name">{{ taskInfo.taskName }}</text> </view> <view class="task-info-body"> <view class="info-item"> <text class="info-label">ä»»å¡ID</text> <text class="info-value">{{ taskInfo.taskId || taskInfo.id }}</text> </view> <view class="info-item"> <text class="info-label">å·¡æ£ä½ç½®</text> <text class="info-value">{{ taskInfo.inspectionLocation || '-' }}</text> </view> <view class="info-item"> <text class="info-label">æ§è¡äºº</text> <text class="info-value">{{ taskInfo.inspector || '-' }}</text> </view> </view> </view> <!-- å¼å¸¸ç¶æéæ© --> <view class="section-card"> <view class="section-title">å·¡æ£ç¶æ</view> <view class="exception-options"> <view class="exception-option" :class="{ active: hasException === false }" @click="setExceptionStatus(false)" > <u-icon name="checkmark-circle" size="20" color="#52c41a"></u-icon> <text class="option-text">æ£å¸¸</text> </view> <view class="exception-option" :class="{ active: hasException === true }" @click="setExceptionStatus(true)" > <u-icon name="close-circle" size="20" color="#ff4d4f"></u-icon> <text class="option-text">åå¨å¼å¸¸</text> </view> </view> </view> <!-- å¼å¸¸æè¿°ï¼ä» å¨å¼å¸¸æ¶æ¾ç¤ºï¼ --> <view class="section-card" v-if="hasException === true"> <view class="section-title">å¼å¸¸æè¿°</view> <textarea v-model="abnormalDescription" class="exception-textarea" maxlength="500" placeholder="请æè¿°å¼å¸¸æ åµ..." /> </view> <!-- åç±»æ ç¾é¡µï¼ä» å¨å¼å¸¸æ¶æ¾ç¤ºï¼ --> <view class="section-card" v-if="hasException === true"> <view class="upload-tabs"> <view class="tab-item" :class="{ active: currentUploadType === 'before' }" @click="switchUploadType('before')" > ç产å </view> <view class="tab-item" :class="{ active: currentUploadType === 'after' }" @click="switchUploadType('after')" > çäº§ä¸ </view> <view class="tab-item" :class="{ active: currentUploadType === 'issue' }" @click="switchUploadType('issue')" > ç产å </view> </view> <!-- å½ååç±»çä¸ä¼ åºå --> <view class="upload-area"> <view class="upload-buttons"> <u-button type="primary" @click="chooseMedia('image')" :loading="uploading" :disabled="getCurrentFiles().length >= uploadConfig.limit" :customStyle="{ marginRight: '10px', flex: 1 }" > <u-icon name="camera" size="18" color="#fff" style="margin-right: 5px"></u-icon> {{ uploading ? 'ä¸ä¼ ä¸...' : 'æç §' }} </u-button> <u-button type="success" @click="chooseMedia('video')" :loading="uploading" :disabled="getCurrentFiles().length >= uploadConfig.limit" :customStyle="{ flex: 1 }" > <uni-icons type="videocam" size="18" color="#fff" style="margin-right: 5px"></uni-icons> {{ uploading ? 'ä¸ä¼ ä¸...' : 'æè§é¢' }} </u-button> </view> <!-- ä¸ä¼ è¿åº¦ --> <view v-if="uploading" class="upload-progress"> <u-line-progress :percentage="uploadProgress" :showText="true" activeColor="#409eff"></u-line-progress> </view> <!-- å½ååç±»çæä»¶å表 --> <view v-if="getCurrentFiles().length > 0" class="file-list"> <view v-for="(file, index) in getCurrentFiles()" :key="index" class="file-item"> <view class="file-preview-container"> <image v-if="file.type === 'image' || (file.type !== 'video' && !file.type)" :src="file.url || file.tempFilePath || file.path || file.downloadUrl" class="file-preview" mode="aspectFill" /> <view v-else-if="file.type === 'video'" class="video-preview"> <uni-icons type="videocam" size="18" color="#fff" style="margin-right: 5px"></uni-icons> <text class="video-text">è§é¢</text> </view> <!-- å é¤æé® --> <view class="delete-btn" @click="removeFile(index)"> <u-icon name="close" size="12" color="#fff"></u-icon> </view> </view> <view class="file-info"> <text class="file-name">{{ file.bucketFilename || file.name || (file.type === 'image' ? 'å¾ç' : 'è§é¢') }}</text> <text class="file-size">{{ formatFileSize(file.size) }}</text> </view> </view> </view> <view v-if="getCurrentFiles().length === 0" class="empty-state"> <text>è¯·éæ©è¦ä¸ä¼ ç{{ getUploadTypeText() }}å¾çæè§é¢</text> </view> </view> <!-- ç»è®¡ä¿¡æ¯ --> <view class="upload-summary"> <text class="summary-text"> ç产å: {{ beforeModelValue.length }}个æä»¶ | ç产ä¸: {{ afterModelValue.length }}个æä»¶ | ç产å: {{ issueModelValue.length }}个æä»¶ </text> </view> </view> <!-- æ£å¸¸ç¶ææç¤º --> <view class="normal-tip-card" v-if="hasException === false"> <u-icon name="info-circle" size="60" color="#52c41a"></u-icon> <text class="tip-text">设å¤è¿è¡æ£å¸¸ï¼æ éä¸ä¼ ç §ç</text> </view> </view> <!-- åºé¨æé® --> <view class="footer-buttons"> <u-button @click="goBack" :customStyle="{ marginRight: '10px' }">åæ¶</u-button> <u-button v-if="hasException === true" type="warning" @click="goToRepair" :customStyle="{ marginRight: '10px' }"> æ°å¢æ¥ä¿® </u-button> <u-button type="primary" @click="submitUpload">æäº¤</u-button> </view> </view> </template> <script setup> import { ref, computed, onMounted } from 'vue'; import { onLoad } from '@dcloudio/uni-app'; import PageHeader from '@/components/PageHeader.vue'; import { uploadInspectionTask } from '@/api/inspectionManagement'; import { getToken } from '@/utils/auth'; import config from '@/config'; // ä»»å¡ä¿¡æ¯ const taskInfo = ref(null); // ä¸ä¼ ç¸å ³ç¶æ const uploading = ref(false); const uploadProgress = ref(0); // ä¸ä¸ªåç±»çä¸ä¼ ç¶æ const beforeModelValue = ref([]); // ç产å const afterModelValue = ref([]); // çäº§ä¸ const issueModelValue = ref([]); // ç产å // å½åæ¿æ´»çä¸ä¼ ç±»å const currentUploadType = ref('before'); // 'before', 'after', 'issue' // å¼å¸¸ç¶æ const hasException = ref(null); // null: æªéæ©, true: åå¨å¼å¸¸, false: æ£å¸¸ // å¼å¸¸æè¿° const abnormalDescription = ref(''); // ä¸ä¼ é ç½® const uploadConfig = { action: '/file/upload', limit: 10, fileSize: 50, // MB fileType: ['jpg', 'jpeg', 'png', 'mp4', 'mov'], maxVideoDuration: 60, // ç§ }; // 计ç®ä¸ä¼ URL const uploadFileUrl = computed(() => { const baseUrl = config.baseUrl; return baseUrl + uploadConfig.action; }); // 页é¢å è½½ onLoad((options) => { if (options.taskInfo) { try { taskInfo.value = JSON.parse(decodeURIComponent(options.taskInfo)); } catch (e) { console.error('è§£æä»»å¡ä¿¡æ¯å¤±è´¥:', e); } } }); // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; // 忢ä¸ä¼ ç±»å const switchUploadType = (type) => { currentUploadType.value = type; }; // è·åå½ååç±»çæä»¶å表 const getCurrentFiles = () => { switch (currentUploadType.value) { case 'before': return beforeModelValue.value || []; case 'after': return afterModelValue.value || []; case 'issue': return issueModelValue.value || []; default: return []; } }; // è·åä¸ä¼ ç±»åææ¬ const getUploadTypeText = () => { switch (currentUploadType.value) { case 'before': return 'ç产å'; case 'after': return 'ç产ä¸'; case 'issue': return 'ç产å'; default: return ''; } }; // 设置å¼å¸¸ç¶æ const setExceptionStatus = (status) => { hasException.value = status; }; // è·³è½¬å°æ°å¢æ¥ä¿®é¡µé¢ const goToRepair = () => { try { const taskData = { taskId: taskInfo.value?.taskId || taskInfo.value?.id, taskName: taskInfo.value?.taskName, inspectionLocation: taskInfo.value?.inspectionLocation, inspector: taskInfo.value?.inspector, uploadedFiles: { before: beforeModelValue.value, after: afterModelValue.value, issue: issueModelValue.value, }, }; uni.setStorageSync('repairTaskInfo', JSON.stringify(taskData)); uni.navigateTo({ url: '/pages/equipmentManagement/repair/add', }); } catch (error) { console.error('跳转æ¥ä¿®é¡µé¢å¤±è´¥:', error); uni.showToast({ title: '跳转失败ï¼è¯·éè¯', icon: 'error', }); } }; // æäº¤ä¸ä¼ const submitUpload = async () => { try { // æ£æ¥æ¯å¦éæ©äºå¼å¸¸ç¶æ if (hasException.value === null) { uni.showToast({ title: 'è¯·éæ©å·¡æ£ç¶æ', icon: 'none', }); return; } // 妿æ¯å¼å¸¸ç¶æï¼æ£æ¥æ¯å¦æä¸ä¼ æä»¶åæè¿° if (hasException.value === true) { const totalFiles = beforeModelValue.value.length + afterModelValue.value.length + issueModelValue.value.length; if (totalFiles === 0) { uni.showToast({ title: '请ä¸ä¼ å¼å¸¸ç §ç', icon: 'none', }); return; } // æ£æ¥æ¯å¦å¡«åäºå¼å¸¸æè¿° if (!abnormalDescription.value.trim()) { uni.showToast({ title: '请填åå¼å¸¸æè¿°', icon: 'none', }); return; } } // æ¾ç¤ºæäº¤ä¸çå è½½æç¤º uni.showLoading({ title: 'æäº¤ä¸...', mask: true, }); // æç §é»è¾åå¹¶ææåç±»çæä»¶ let arr = []; if (beforeModelValue.value.length > 0) { arr.push(...beforeModelValue.value); } if (afterModelValue.value.length > 0) { arr.push(...afterModelValue.value); } if (issueModelValue.value.length > 0) { arr.push(...issueModelValue.value); } // ä¼ ç»å端çä¸´æ¶æä»¶IDå表 let tempFileIds = []; if (arr !== null && arr.length > 0) { tempFileIds = arr .map((item) => item?.tempId ?? item?.tempFileId ?? item?.id) .filter((v) => v !== undefined && v !== null && v !== ''); } // æäº¤æ°æ® const submitData = { ...taskInfo.value, storageBlobDTO: arr, hasException: hasException.value, abnormalDescription: abnormalDescription.value, tempFileIds: tempFileIds, }; const result = await uploadInspectionTask(submitData); // æ£æ¥æäº¤ç»æ if (result && (result.code === 200 || result.success)) { uni.hideLoading(); uni.showToast({ title: 'æäº¤æå', icon: 'success', }); // è¿ååè¡¨é¡µå¹¶å·æ° setTimeout(() => { uni.navigateBack(); }, 500); } else { uni.hideLoading(); uni.showToast({ title: result?.msg || result?.message || 'æäº¤å¤±è´¥', icon: 'error', }); } } catch (error) { console.error('æäº¤ä¸ä¼ 失败:', error); uni.hideLoading(); uni.showToast({ title: error?.message || 'æäº¤å¤±è´¥', icon: 'error', }); } }; // æ ¼å¼åæä»¶å¤§å° const formatFileSize = (size) => { if (!size) return '0 B'; const units = ['B', 'KB', 'MB', 'GB']; let index = 0; let fileSize = size; while (fileSize >= 1024 && index < units.length - 1) { fileSize /= 1024; index++; } return `${fileSize.toFixed(2)} ${units[index]}`; }; // æç §/æè§é¢ const chooseMedia = (type) => { if (getCurrentFiles().length >= uploadConfig.limit) { uni.showToast({ title: `æå¤åªè½éæ©${uploadConfig.limit}个æä»¶`, icon: 'none', }); return; } const remaining = uploadConfig.limit - getCurrentFiles().length; // ä¼å ä½¿ç¨ chooseMedia if (typeof uni.chooseMedia === 'function') { uni.chooseMedia({ count: Math.min(remaining, 1), mediaType: [type || 'image'], sizeType: ['compressed', 'original'], sourceType: ['camera'], success: (res) => { try { const files = res?.tempFiles || []; if (!files.length) throw new Error('æªè·åå°æä»¶'); files.forEach((tf, idx) => { const filePath = tf.tempFilePath || tf.path || ''; const fileType = tf.fileType || type || 'image'; const ext = fileType === 'video' ? 'mp4' : 'jpg'; const file = { tempFilePath: filePath, path: filePath, type: fileType, name: `${fileType}_${Date.now()}_${idx}.${ext}`, size: tf.size || 0, duration: tf.duration || 0, createTime: Date.now(), }; uploadFile(file); }); } catch (err) { uni.showToast({ title: err.message || 'å¤çæä»¶å¤±è´¥', icon: 'none' }); } }, fail: (err) => { console.error('éæ©åªä½å¤±è´¥:', err); uni.showToast({ title: 'éæ©å¤±è´¥', icon: 'none' }); }, }); } else { // éçº§æ¹æ¡ if (type === 'video') { uni.chooseVideo({ sourceType: ['camera'], success: (res) => { const file = { tempFilePath: res.tempFilePath, path: res.tempFilePath, type: 'video', name: `video_${Date.now()}.mp4`, size: res.size || 0, duration: res.duration || 0, createTime: Date.now(), }; uploadFile(file); }, fail: () => { uni.showToast({ title: 'éæ©è§é¢å¤±è´¥', icon: 'none' }); }, }); } else { uni.chooseImage({ count: Math.min(remaining, 9), sizeType: ['compressed'], sourceType: ['camera'], success: (res) => { const list = res.tempFilePaths || res.tempFiles || []; list.forEach((src, idx) => { const path = typeof src === 'string' ? src : src.path; const file = { tempFilePath: path, path: path, type: 'image', name: `image_${Date.now()}_${idx}.jpg`, size: 0, createTime: Date.now(), }; uploadFile(file); }); }, fail: () => { uni.showToast({ title: 'éæ©å¾ç失败', icon: 'none' }); }, }); } } }; // ä¸ä¼ å个æä»¶ const uploadFile = (file) => { const token = getToken(); if (!token) { uni.showToast({ title: 'ç¨æ·æªç»å½', icon: 'none' }); return; } uploading.value = true; uploadProgress.value = 0; const uploadTask = uni.uploadFile({ url: uploadFileUrl.value, filePath: file.tempFilePath, name: 'file', header: { Authorization: `Bearer ${token}`, }, formData: { type: getTabType(), }, success: (res) => { try { const data = JSON.parse(res.data); if (data.code === 200) { const uploadedFile = { ...file, url: data.data.url, tempId: data.data.tempId || data.data.id, status: 'success', }; // æ ¹æ®å½åç±»åæ·»å å°å¯¹åºæ°ç» if (currentUploadType.value === 'before') { beforeModelValue.value.push(uploadedFile); } else if (currentUploadType.value === 'after') { afterModelValue.value.push(uploadedFile); } else if (currentUploadType.value === 'issue') { issueModelValue.value.push(uploadedFile); } uni.showToast({ title: 'ä¸ä¼ æå', icon: 'success' }); } else { uni.showToast({ title: data.msg || 'ä¸ä¼ 失败', icon: 'none' }); } } catch (e) { uni.showToast({ title: 'è§£æååºå¤±è´¥', icon: 'none' }); } }, fail: (err) => { console.error('ä¸ä¼ 失败:', err); uni.showToast({ title: 'ä¸ä¼ 失败', icon: 'none' }); }, complete: () => { uploading.value = false; }, }); // çå¬ä¸ä¼ è¿åº¦ uploadTask.onProgressUpdate((res) => { uploadProgress.value = res.progress; }); }; // è·åtypeå¼ const getTabType = () => { switch (currentUploadType.value) { case 'before': return 10; case 'after': return 11; case 'issue': return 12; default: return 10; } }; // å 餿件 const removeFile = (index) => { const files = getCurrentFiles(); files.splice(index, 1); }; </script> <style scoped> .inspection-upload-page { min-height: 100vh; background-color: #f5f5f5; padding-bottom: 80px; } .upload-content { padding: 15px; } /* ä»»å¡ä¿¡æ¯å¡ç */ .task-info-card { background: #fff; border-radius: 12px; padding: 15px; margin-bottom: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .task-info-header { margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; } .task-name { font-size: 16px; font-weight: 600; color: #333; } .task-info-body { display: flex; flex-direction: column; gap: 8px; } .info-item { display: flex; justify-content: space-between; align-items: center; } .info-label { font-size: 13px; color: #999; } .info-value { font-size: 13px; color: #666; } /* éç¨å¡çæ ·å¼ */ .section-card { background: #fff; border-radius: 12px; padding: 15px; margin-bottom: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .section-title { font-size: 14px; font-weight: 600; color: #333; margin-bottom: 12px; } /* å¼å¸¸ç¶æéæ© */ .exception-options { display: flex; gap: 12px; } .exception-option { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 16px; background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; } .exception-option.active { border-color: #409eff; background: #f0f8ff; } .option-text { font-size: 14px; color: #333; font-weight: 500; } /* å¼å¸¸æè¿° */ .exception-textarea { width: 100%; min-height: 100px; padding: 12px; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; font-size: 14px; color: #333; resize: none; box-sizing: border-box; } .exception-textarea:focus { outline: none; border-color: #409eff; background: #fff; } /* åç±»æ ç¾é¡µ */ .upload-tabs { display: flex; gap: 10px; margin-bottom: 15px; } .tab-item { flex: 1; padding: 10px; text-align: center; background: #f5f5f5; border-radius: 6px; font-size: 13px; color: #666; cursor: pointer; transition: all 0.3s; } .tab-item.active { background: #409eff; color: #fff; } /* ä¸ä¼ åºå */ .upload-area { padding: 10px 0; } .upload-buttons { display: flex; gap: 10px; margin-bottom: 15px; } .upload-progress { margin-bottom: 15px; } /* æä»¶å表 */ .file-list { display: flex; flex-wrap: wrap; gap: 10px; } .file-item { width: calc(33.33% - 7px); } .file-preview-container { position: relative; width: 100%; aspect-ratio: 1; border-radius: 8px; overflow: hidden; background: #f5f5f5; } .file-preview { width: 100%; height: 100%; object-fit: cover; } .video-preview { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #333; } .video-text { font-size: 12px; color: #fff; margin-top: 5px; } .delete-btn { position: absolute; top: 5px; right: 5px; width: 22px; height: 22px; background: rgba(0, 0, 0, 0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center; } .file-info { margin-top: 5px; } .file-name { display: block; font-size: 11px; color: #666; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .file-size { display: block; font-size: 10px; color: #999; margin-top: 2px; } .empty-state { text-align: center; padding: 30px; color: #999; font-size: 13px; } /* ç»è®¡ä¿¡æ¯ */ .upload-summary { margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 6px; border-left: 3px solid #409eff; } .summary-text { font-size: 12px; color: #666; } /* æ£å¸¸ç¶ææç¤º */ .normal-tip-card { background: #f6ffed; border: 2px dashed #b7eb8f; border-radius: 12px; padding: 50px 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; margin-bottom: 15px; } .normal-tip-card .tip-text { margin-top: 15px; font-size: 16px; color: #52c41a; font-weight: 500; } /* åºé¨æé® */ .footer-buttons { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 15px; background: #fff; border-top: 1px solid #f0f0f0; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); } </style>