src/views/customerService/afterSalesHandling/components/formDia.vue
@@ -82,6 +82,33 @@ </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="评分:" prop="rating"> <el-rate v-model="form.rating" :disabled="operationType === 'view'" :max="5" show-text :texts="['非常不满意', '不满意', '一般', '满意', '非常满意']" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="评价内容描述:" prop="evaluation"> <el-input v-model="form.evaluation" placeholder="请输入评价内容描述" clearable :disabled="operationType === 'view'" type="textarea" :rows="3" /> </el-form-item> </el-col> </el-row> </el-form> <el-row :gutter="30"> <el-col :span="12"> @@ -148,6 +175,8 @@ disposeUserId: "", disDate: "", disRes: "", rating: null, evaluation: "", }, rules: { feedbackDate: [{required: true, message: "请选择", trigger: "change"}], src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -326,7 +326,7 @@ form.value.customerId = null; } getSalesLedger({ customerName: form.value.customerName, customerId: form.value.customerId, }).then(res => { if (res.code === 200) { associatedSalesOrderNumberOptions.value = res.data.records.map(item => ({ @@ -434,7 +434,7 @@ form.value = { ...row }; if (form.value.customerName) { const res = await getSalesLedger({ customerName: form.value.customerName, customerId: form.value.customerId, }); if (res?.code === 200) { console.log(res); src/views/customerService/feedbackRegistration/index.vue
@@ -149,14 +149,14 @@ { icon: markRaw(FolderOpened), count: 0, label: "已处理", label: "待处理", color: "#ff9a2e", bgColor: "#fff5e6", }, { icon: markRaw(UserFilled), count: 0, label: "已完成", label: "已处理", color: "#00b42a", bgColor: "#e6f7ed", }, @@ -424,8 +424,8 @@ if (res.code === 200) { const statsData = Array.isArray(res.data) ? res.data : []; statsList.value[0].count = getStatsCountByStatus(statsData, 3); statsList.value[1].count = getStatsCountByStatus(statsData, 2); statsList.value[2].count = getStatsCountByStatus(statsData, 1); statsList.value[1].count = getStatsCountByStatus(statsData, 1); statsList.value[2].count = getStatsCountByStatus(statsData, 2); } }); }; src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
@@ -1,117 +1,131 @@ <template> <FormDialog v-model="dialogVisible" title="上传巡检记录" width="980px" @close="handleClose" @cancel="handleClose" > <FormDialog v-model="dialogVisible" :title="operationType === 'view' ? '巡检记录详情' : '上传巡检记录'" width="980px" :operation-type="operationType" @close="handleClose" @cancel="handleClose"> <main class="upload-content"> <el-card v-if="taskInfo" class="section-card"> <el-descriptions :column="1" border> <el-card v-if="taskInfo" class="section-card"> <el-descriptions :column="2" border> <el-descriptions-item label="巡检任务名称"> {{ taskInfo.taskName || "-" }} </el-descriptions-item> <el-descriptions-item label="巡检项目"> {{ taskInfo.inspectionProject || "-" }} </el-descriptions-item> <el-descriptions-item label="执行巡检人"> <template v-if="formattedInspector && formattedInspector.length"> <el-tag v-for="tag in formattedInspector" :key="tag" size="small" style="margin-right: 4px"> {{ tag }} </el-tag> </template> <span v-else>--</span> </el-descriptions-item> <el-descriptions-item label="频次"> {{ formatFrequencyType(taskInfo.frequencyType) || "-" }} </el-descriptions-item> <el-descriptions-item label="开始日期与时间"> {{ formatFrequencyDetail(taskInfo.frequencyDetail) || "-" }} </el-descriptions-item> <el-descriptions-item label="登记人"> {{ taskInfo.registrant || "-" }} </el-descriptions-item> <el-descriptions-item label="登记日期"> {{ formatDateTime(taskInfo.createTime) || "-" }} </el-descriptions-item> <el-descriptions-item label="备注"> {{ taskInfo.remarks || "-" }} </el-descriptions-item> </el-descriptions> </el-card> <el-card class="section-card"> <h3>巡检状态</h3> <el-radio-group v-model="hasException"> <el-radio-group v-model="hasException" :disabled="operationType === 'view'"> <el-radio-button :value="false">正常</el-radio-button> <el-radio-button :value="true">存在异常</el-radio-button> </el-radio-group> </el-card> <el-card v-if="hasException === true" class="section-card"> <el-card v-if="hasException === true" class="section-card"> <h3>异常描述</h3> <el-input v-model="abnormalDescription" type="textarea" maxlength="500" show-word-limit :rows="4" placeholder="请描述异常情况..." /> <el-input v-model="abnormalDescription" type="textarea" maxlength="500" show-word-limit :rows="4" :disabled="operationType === 'view'" placeholder="请描述异常情况..." /> </el-card> <el-card v-if="hasException === true" class="section-card"> <el-tabs v-model="currentUploadType"> <el-tab-pane label="生产前" name="before" /> <el-tab-pane label="生产中" name="after" /> <el-tab-pane label="生产后" name="issue" /> </el-tabs> <div class="upload-buttons"> <el-upload :show-file-list="false" :http-request="uploadFile" :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading" accept="image/*" > <el-button type="primary" :loading="uploading"> <el-icon><Camera /></el-icon> <el-card v-if="hasException === true" class="section-card"> <div class="upload-buttons" v-if="operationType !== 'view'"> <el-upload :show-file-list="false" :http-request="uploadFile" :disabled="beforeModelValue.length >= uploadConfig.limit || uploading" accept="image/*"> <el-button type="primary" :loading="uploading"> <el-icon> <Camera /> </el-icon> 选择图片 </el-button> </el-upload> <el-upload :show-file-list="false" :http-request="uploadFile" :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading" accept="video/*" > <el-button type="success" :loading="uploading"> <el-icon><VideoCamera /></el-icon> <el-upload :show-file-list="false" :http-request="uploadFile" :disabled="beforeModelValue.length >= uploadConfig.limit || uploading" accept="video/*"> <el-button type="success" :loading="uploading"> <el-icon> <VideoCamera /> </el-icon> 选择视频 </el-button> </el-upload> </div> <el-progress v-if="uploading" :percentage="uploadProgress" class="upload-progress" /> <div v-if="getCurrentFiles().length" class="file-list"> <div v-for="(file, index) in getCurrentFiles()" :key="file.uid || file.id || index" class="file-item" > <el-progress v-if="uploading" :percentage="uploadProgress" class="upload-progress" /> <div v-if="beforeModelValue.length" class="file-list"> <div v-for="(file, index) in beforeModelValue" :key="file.uid || file.id || index" class="file-item"> <div class="file-preview-container"> <el-image v-if="file.type === 'image' || !file.type" :src="file.url || file.tempFilePath || file.path || file.downloadUrl" fit="cover" class="file-preview" :preview-src-list="[file.url || file.tempFilePath || file.path || file.downloadUrl]" preview-teleported /> <div v-else class="video-preview" @click="previewVideo(file)"> <el-icon><VideoCamera /></el-icon> <el-image v-if="file.type === 'image' || !file.type" :src="file.url || file.tempFilePath || file.path || file.downloadUrl" fit="cover" class="file-preview" :preview-src-list="[file.url || file.tempFilePath || file.path || file.downloadUrl]" preview-teleported /> <div v-else class="video-preview" @click="previewVideo(file)"> <el-icon> <VideoCamera /> </el-icon> <span>视频</span> </div> <el-button class="delete-btn" type="danger" circle size="small" @click="removeFile(index)" > <el-icon><Close /></el-icon> <el-button class="delete-btn" type="danger" circle size="small" v-if="operationType !== 'view'" @click="removeFile(index)"> <el-icon> <Close /> </el-icon> </el-button> </div> <div class="file-info"> <div class="file-name"> {{ file.bucketFilename || file.name || (file.type === "image" ? "图片" : "视频") }} @@ -120,561 +134,559 @@ </div> </div> </div> <el-empty v-else :description="`请选择要上传的${getUploadTypeText()}图片或视频`" /> <el-alert class="upload-summary" type="info" :closable="false" :title="`生产前:${beforeModelValue.length}个文件 | 生产中:${afterModelValue.length}个文件 | 生产后:${issueModelValue.length}个文件`" /> <el-empty v-else :description="operationType === 'view' ? '暂无异常照片或视频' : '请选择要上传的巡检图片或视频'" /> </el-card> <el-result v-if="hasException === false" icon="success" title="设备运行正常" sub-title="无需上传照片" /> <el-result v-if="hasException === false" icon="success" title="设备运行正常" :sub-title="operationType === 'view' ? '' : '无需上传照片'" /> </main> <template #footer> <footer class="footer-buttons"> <el-button type="primary" @click="submitUpload">提交</el-button> <el-button v-if="hasException === true" type="warning" @click="goToRepair"> <el-button type="primary" v-if="operationType !== 'view'" @click="submitUpload">提交</el-button> <el-button v-if="hasException === true && operationType !== 'view'" type="warning" @click="goToRepair"> 新增报修 </el-button> <el-button @click="handleClose">取消</el-button> <el-button @click="handleClose">{{ operationType === 'view' ? '关闭' : '取消' }}</el-button> </footer> </template> </FormDialog> <el-dialog v-model="showVideoDialog" :title="currentVideoFile?.originalFilename || currentVideoFile?.name || '视频预览'" width="720px" > <video v-if="currentVideoFile" :src="currentVideoFile.url || currentVideoFile.downloadUrl" class="video-player" controls autoplay /> <el-dialog v-model="showVideoDialog" :title="currentVideoFile?.originalFilename || currentVideoFile?.name || '视频预览'" width="720px"> <video v-if="currentVideoFile" :src="currentVideoFile.url || currentVideoFile.downloadUrl" class="video-player" controls autoplay /> </el-dialog> </template> <script setup> import { computed, ref } from "vue"; import { useRouter } from "vue-router"; import { ElLoading, ElMessage, ElMessageBox } from "element-plus"; import { Camera, Close, VideoCamera } from "@element-plus/icons-vue"; import axios from "axios"; import FormDialog from "@/components/Dialog/FormDialog.vue"; import { uploadInspectionTask } from "@/api/inspectionManagement/index.js"; import { getToken } from "@/utils/auth"; import { computed, ref } from "vue"; import { useRouter } from "vue-router"; import { ElLoading, ElMessage, ElMessageBox } from "element-plus"; import { Camera, Close, VideoCamera } from "@element-plus/icons-vue"; import axios from "axios"; import dayjs from "dayjs"; import FormDialog from "@/components/Dialog/FormDialog.vue"; import { uploadInspectionTask } from "@/api/inspectionManagement/index.js"; import { getToken } from "@/utils/auth"; const emit = defineEmits(["closeDia", "success"]); const router = useRouter(); const emit = defineEmits(["closeDia", "success"]); const router = useRouter(); const dialogVisible = ref(false); const taskInfo = ref(null); const uploading = ref(false); const uploadProgress = ref(0); const dialogVisible = ref(false); const taskInfo = ref(null); const uploading = ref(false); const uploadProgress = ref(0); const operationType = ref("add"); // add, view const beforeModelValue = ref([]); const afterModelValue = ref([]); const issueModelValue = ref([]); const beforeModelValue = ref([]); const currentUploadType = ref("before"); const hasException = ref(null); const abnormalDescription = ref(""); const showVideoDialog = ref(false); const currentVideoFile = ref(null); const uploadConfig = { action: "/common/upload", limit: 10, fileSize: 50, fileType: ["jpg", "jpeg", "png", "mp4", "mov"], }; const uploadFileUrl = computed( () => `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}` ); const processFileUrl = fileUrl => { if (!fileUrl) return ""; let currentUrl = String(fileUrl); if (currentUrl.includes("\\")) { const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads"); if (uploadsIndex > -1) { currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`; } else { const fileName = currentUrl.split("\\").pop(); currentUrl = `/uploads/${fileName}`; const formattedInspector = computed(() => { if (!taskInfo.value?.inspector) return []; if (Array.isArray(taskInfo.value.inspector)) return taskInfo.value.inspector; if (typeof taskInfo.value.inspector === "string") { return taskInfo.value.inspector .split(",") .map(s => s.trim()) .filter(s => s); } } if (currentUrl && !currentUrl.startsWith("http")) { if (!currentUrl.startsWith("/")) { currentUrl = `/${currentUrl}`; } currentUrl = __BASE_API__ + currentUrl; } return currentUrl; }; const normalizeList = (list, fileType) => { if (!Array.isArray(list)) return []; return list.filter(Boolean).map(item => { let currentType = item.type; if (!currentType && item.contentType) { currentType = item.contentType.startsWith("video") ? "video" : "image"; } else if (!currentType) { currentType = fileType || "image"; } return { ...item, url: processFileUrl(item.url || item.previewURL || item.downloadUrl || item.path || ""), downloadUrl: processFileUrl( item.downloadUrl || item.url || item.previewURL || item.path || "" ), name: item.name || item.originalFilename || item.bucketFilename, tempId: item.tempId || item.id || item.tempFileId, tempFileId: item.tempFileId || item.tempId || item.id, size: item.size || item.byteSize || 0, type: currentType, status: "success", uid: item.uid || `${Date.now()}-${Math.random()}`, }; }); }; const resetState = () => { taskInfo.value = null; beforeModelValue.value = []; afterModelValue.value = []; issueModelValue.value = []; currentUploadType.value = "before"; hasException.value = null; abnormalDescription.value = ""; uploading.value = false; uploadProgress.value = 0; showVideoDialog.value = false; currentVideoFile.value = null; }; const openDialog = row => { const raw = JSON.parse(JSON.stringify(row?.__raw || row || {})); taskInfo.value = raw; beforeModelValue.value = normalizeList( raw.commonFileListBeforeVO || raw.commonFileListBefore || [], "image" ); afterModelValue.value = normalizeList( raw.commonFileListVO || raw.commonFileList || [], "image" ); issueModelValue.value = normalizeList( raw.commonFileListAfterVO || raw.commonFileListAfter || [], "image" ); abnormalDescription.value = raw.abnormalDescription || ""; if (raw.hasException !== undefined && raw.hasException !== null) { hasException.value = raw.hasException; } else if (raw.inspectionResult !== undefined && raw.inspectionResult !== null) { hasException.value = String(raw.inspectionResult) === "0"; } else { hasException.value = null; } if ( hasException.value !== true && (beforeModelValue.value.length || afterModelValue.value.length || issueModelValue.value.length) ) { hasException.value = true; } dialogVisible.value = true; }; const handleClose = () => { dialogVisible.value = false; resetState(); emit("closeDia"); }; const getCurrentFiles = () => { if (currentUploadType.value === "before") return beforeModelValue.value; if (currentUploadType.value === "after") return afterModelValue.value; if (currentUploadType.value === "issue") return issueModelValue.value; return []; }; const getUploadTypeText = () => { if (currentUploadType.value === "before") return "生产前"; if (currentUploadType.value === "after") return "生产中"; if (currentUploadType.value === "issue") return "生产后"; return ""; }; const getTabType = () => { if (currentUploadType.value === "before") return 10; if (currentUploadType.value === "after") return 11; if (currentUploadType.value === "issue") return 12; return 10; }; const previewVideo = file => { currentVideoFile.value = file; showVideoDialog.value = true; }; const uploadFile = async uploadRequest => { const rawFile = uploadRequest.file; if (getCurrentFiles().length >= uploadConfig.limit) { ElMessage.warning(`最多只能选择${uploadConfig.limit}个文件`); return; } const ext = rawFile.name.split(".").pop()?.toLowerCase(); if (!uploadConfig.fileType.includes(ext)) { ElMessage.warning(`文件格式不支持,请上传 ${uploadConfig.fileType.join("/")} 格式`); return; } if (rawFile.size > uploadConfig.fileSize * 1024 * 1024) { ElMessage.warning(`文件大小不能超过 ${uploadConfig.fileSize}MB`); return; } const token = getToken(); if (!token) { ElMessage.warning("用户未登录"); return; } const formData = new FormData(); formData.append("files", rawFile); formData.append("type", getTabType()); uploading.value = true; uploadProgress.value = 0; try { const { data } = await axios.post(uploadFileUrl.value, formData, { headers: { Authorization: `Bearer ${token}`, "Content-Type": "multipart/form-data", }, onUploadProgress: event => { if (event.total) { uploadProgress.value = Math.round((event.loaded / event.total) * 100); } }, }); if (data.code !== 200) { ElMessage.error(data.msg || "上传失败"); return; } const resultData = Array.isArray(data.data) ? data.data[0] : data.data; const finalUrl = processFileUrl( resultData.url || resultData.previewURL || resultData.downloadUrl || "" ); const finalName = resultData.name || resultData.originalFilename || resultData.bucketFilename; const finalId = resultData.tempId || resultData.id || resultData.tempFileId; const uploadedFile = { ...resultData, url: finalUrl, downloadUrl: finalUrl, name: finalName, tempId: finalId, tempFileId: resultData.tempFileId || finalId, size: rawFile.size || resultData.size || resultData.byteSize || 0, type: rawFile.type?.startsWith("video") ? "video" : "image", status: "success", uid: `${Date.now()}-${Math.random()}`, }; getCurrentFiles().push(uploadedFile); ElMessage.success("上传成功"); } catch (error) { ElMessage.error(error?.message || "上传失败"); } finally { uploading.value = false; } }; const buildFileItem = item => ({ id: item?.id, tempId: item?.tempId, tempFileId: item?.tempFileId, url: item?.downloadUrl || item?.url || "", downloadUrl: item?.downloadUrl || item?.url || "", name: item?.name, bucketFilename: item?.bucketFilename || item?.name, originalFilename: item?.originalFilename || item?.name, size: item?.size || 0, byteSize: item?.byteSize || item?.size || 0, contentType: item?.contentType || "", type: item?.type, }); const submitUpload = async () => { if (hasException.value === null) { ElMessage.warning("请选择巡检状态"); return; } if (hasException.value === true) { const totalFiles = beforeModelValue.value.length + afterModelValue.value.length + issueModelValue.value.length; if (!totalFiles) { ElMessage.warning("请上传异常照片或视频"); return; } if (!abnormalDescription.value.trim()) { ElMessage.warning("请填写异常描述"); return; } } const loading = ElLoading.service({ text: "提交中...", background: "rgba(0, 0, 0, 0.3)", return [taskInfo.value.inspector]; }); try { const allFiles = [ ...beforeModelValue.value, ...afterModelValue.value, ...issueModelValue.value, ]; const tempFileIds = allFiles .map(item => item?.tempId ?? item?.tempFileId ?? item?.id) .filter(Boolean); const { createTime, updateTime, storageBlobDTO, commonFileListAfterVO, commonFileListVO, commonFileListBeforeVO, commonFileListAfter, commonFileList, commonFileListBefore, __raw, ...baseTaskInfo } = taskInfo.value || {}; const submitData = { ...baseTaskInfo, commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem), commonFileListDTO: afterModelValue.value.map(buildFileItem), commonFileListAfterDTO: issueModelValue.value.map(buildFileItem), hasException: hasException.value, inspectionResult: hasException.value ? 0 : 1, abnormalDescription: abnormalDescription.value, tempFileIds, const formatFrequencyType = type => { const mapping = { DAILY: "每日", WEEKLY: "每周", MONTHLY: "每月", QUARTERLY: "季度", }; const result = await uploadInspectionTask(submitData); if (result && (result.code === 200 || result.success)) { ElMessage.success("提交成功"); dialogVisible.value = false; resetState(); emit("success"); emit("closeDia"); } else { ElMessage.error(result?.msg || result?.message || "提交失败"); } } catch (error) { ElMessage.error(error?.message || "提交失败"); } finally { loading.close(); } }; const removeFile = async index => { try { await ElMessageBox.confirm("确定要删除这个文件吗?", "确认删除", { type: "warning", }); getCurrentFiles().splice(index, 1); } catch {} }; const goToRepair = () => { const taskData = { taskId: taskInfo.value?.taskId || taskInfo.value?.id, taskName: taskInfo.value?.taskName, inspectionLocation: taskInfo.value?.inspectionLocation, inspector: taskInfo.value?.inspector, hasException: hasException.value, inspectionResult: hasException.value ? 0 : 1, commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem), commonFileListDTO: afterModelValue.value.map(buildFileItem), commonFileListAfterDTO: issueModelValue.value.map(buildFileItem), uploadedFiles: { before: beforeModelValue.value, after: afterModelValue.value, issue: issueModelValue.value, }, return mapping[type] || type; }; sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskData)); router.push("/equipmentManagement/repair/add"); }; const formatFrequencyDetail = detail => { if (typeof detail !== "string") return detail; const replacements = { MON: "周一", TUE: "周二", WED: "周三", THU: "周四", FRI: "周五", SAT: "周六", SUN: "周日", }; return detail.replace( /MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match] ); }; const formatFileSize = size => { if (!size) return "0 B"; const formatDateTime = date => { if (!date) return "-"; return dayjs(date).format("YYYY-MM-DD HH:mm:ss"); }; const units = ["B", "KB", "MB", "GB"]; let index = 0; let fileSize = size; const hasException = ref(null); const abnormalDescription = ref(""); while (fileSize >= 1024 && index < units.length - 1) { fileSize /= 1024; index += 1; } const showVideoDialog = ref(false); const currentVideoFile = ref(null); return `${fileSize.toFixed(2)} ${units[index]}`; }; const uploadConfig = { action: "/common/upload", limit: 10, fileSize: 50, fileType: ["jpg", "jpeg", "png", "mp4", "mov"], }; defineExpose({ openDialog, }); const uploadFileUrl = computed( () => `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}` ); const processFileUrl = fileUrl => { if (!fileUrl) return ""; let currentUrl = String(fileUrl); if (currentUrl.includes("\\")) { const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads"); if (uploadsIndex > -1) { currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`; } else { const fileName = currentUrl.split("\\").pop(); currentUrl = `/uploads/${fileName}`; } } if (currentUrl && !currentUrl.startsWith("http")) { if (!currentUrl.startsWith("/")) { currentUrl = `/${currentUrl}`; } currentUrl = __BASE_API__ + currentUrl; } return currentUrl; }; const normalizeList = (list, fileType) => { if (!Array.isArray(list)) return []; return list.filter(Boolean).map(item => { let currentType = item.type; if (!currentType && item.contentType) { currentType = item.contentType.startsWith("video") ? "video" : "image"; } else if (!currentType) { currentType = fileType || "image"; } return { ...item, url: processFileUrl( item.url || item.previewURL || item.downloadUrl || item.path || "" ), downloadUrl: processFileUrl( item.downloadUrl || item.url || item.previewURL || item.path || "" ), name: item.name || item.originalFilename || item.bucketFilename, tempId: item.tempId || item.id || item.tempFileId, tempFileId: item.tempFileId || item.tempId || item.id, size: item.size || item.byteSize || 0, type: currentType, status: "success", uid: item.uid || `${Date.now()}-${Math.random()}`, }; }); }; const resetState = () => { taskInfo.value = null; beforeModelValue.value = []; hasException.value = null; abnormalDescription.value = ""; uploading.value = false; uploadProgress.value = 0; showVideoDialog.value = false; currentVideoFile.value = null; operationType.value = "add"; }; const openDialog = (type, row) => { operationType.value = type || "add"; const raw = JSON.parse(JSON.stringify(row?.__raw || row || {})); taskInfo.value = raw; beforeModelValue.value = normalizeList( raw.commonFileListBeforeVO || raw.commonFileListBefore || [], "image" ); abnormalDescription.value = raw.abnormalDescription || ""; if (raw.hasException !== undefined && raw.hasException !== null) { hasException.value = raw.hasException; } else if ( raw.inspectionResult !== undefined && raw.inspectionResult !== null ) { hasException.value = String(raw.inspectionResult) === "0"; } else { hasException.value = null; } if (hasException.value !== true && beforeModelValue.value.length) { hasException.value = true; } dialogVisible.value = true; }; const handleClose = () => { dialogVisible.value = false; resetState(); emit("closeDia"); }; const previewVideo = file => { currentVideoFile.value = file; showVideoDialog.value = true; }; const uploadFile = async uploadRequest => { const rawFile = uploadRequest.file; if (beforeModelValue.value.length >= uploadConfig.limit) { ElMessage.warning(`最多只能选择${uploadConfig.limit}个文件`); return; } const ext = rawFile.name.split(".").pop()?.toLowerCase(); if (!uploadConfig.fileType.includes(ext)) { ElMessage.warning( `文件格式不支持,请上传 ${uploadConfig.fileType.join("/")} 格式` ); return; } if (rawFile.size > uploadConfig.fileSize * 1024 * 1024) { ElMessage.warning(`文件大小不能超过 ${uploadConfig.fileSize}MB`); return; } const token = getToken(); if (!token) { ElMessage.warning("用户未登录"); return; } const formData = new FormData(); formData.append("files", rawFile); formData.append("type", 10); // 生产前固定为10 uploading.value = true; uploadProgress.value = 0; try { const { data } = await axios.post(uploadFileUrl.value, formData, { headers: { Authorization: `Bearer ${token}`, "Content-Type": "multipart/form-data", }, onUploadProgress: event => { if (event.total) { uploadProgress.value = Math.round((event.loaded / event.total) * 100); } }, }); if (data.code !== 200) { ElMessage.error(data.msg || "上传失败"); return; } const resultData = Array.isArray(data.data) ? data.data[0] : data.data; const finalUrl = processFileUrl( resultData.url || resultData.previewURL || resultData.downloadUrl || "" ); const finalName = resultData.name || resultData.originalFilename || resultData.bucketFilename; const finalId = resultData.tempId || resultData.id || resultData.tempFileId; const uploadedFile = { ...resultData, url: finalUrl, downloadUrl: finalUrl, name: finalName, tempId: finalId, tempFileId: resultData.tempFileId || finalId, size: rawFile.size || resultData.size || resultData.byteSize || 0, type: rawFile.type?.startsWith("video") ? "video" : "image", status: "success", uid: `${Date.now()}-${Math.random()}`, }; beforeModelValue.value.push(uploadedFile); ElMessage.success("上传成功"); } catch (error) { ElMessage.error(error?.message || "上传失败"); } finally { uploading.value = false; } }; const buildFileItem = item => ({ id: item?.id, tempId: item?.tempId, tempFileId: item?.tempFileId, url: item?.downloadUrl || item?.url || "", downloadUrl: item?.downloadUrl || item?.url || "", name: item?.name, bucketFilename: item?.bucketFilename || item?.name, originalFilename: item?.originalFilename || item?.name, size: item?.size || 0, byteSize: item?.byteSize || item?.size || 0, contentType: item?.contentType || "", type: item?.type, }); const submitUpload = async () => { if (hasException.value === null) { ElMessage.warning("请选择巡检状态"); return; } if (hasException.value === true) { const totalFiles = beforeModelValue.value.length; if (!totalFiles) { ElMessage.warning("请上传异常照片或视频"); return; } if (!abnormalDescription.value.trim()) { ElMessage.warning("请填写异常描述"); return; } } const loading = ElLoading.service({ text: "提交中...", background: "rgba(0, 0, 0, 0.3)", }); try { const allFiles = [...beforeModelValue.value]; const tempFileIds = allFiles .map(item => item?.tempId ?? item?.tempFileId ?? item?.id) .filter(Boolean); const { createTime, updateTime, storageBlobDTO, commonFileListAfterVO, commonFileListVO, commonFileListBeforeVO, commonFileListAfter, commonFileList, commonFileListBefore, __raw, ...baseTaskInfo } = taskInfo.value || {}; const submitData = { ...baseTaskInfo, commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem), commonFileListDTO: [], commonFileListAfterDTO: [], hasException: hasException.value, inspectionResult: hasException.value ? 0 : 1, abnormalDescription: abnormalDescription.value, tempFileIds, }; const result = await uploadInspectionTask(submitData); if (result && (result.code === 200 || result.success)) { ElMessage.success("提交成功"); dialogVisible.value = false; resetState(); emit("success"); emit("closeDia"); } else { ElMessage.error(result?.msg || result?.message || "提交失败"); } } catch (error) { ElMessage.error(error?.message || "提交失败"); } finally { loading.close(); } }; const removeFile = async index => { try { await ElMessageBox.confirm("确定要删除这个文件吗?", "确认删除", { type: "warning", }); beforeModelValue.value.splice(index, 1); } catch {} }; const goToRepair = () => { const taskData = { taskId: taskInfo.value?.taskId || taskInfo.value?.id, taskName: taskInfo.value?.taskName, inspectionLocation: taskInfo.value?.inspectionLocation, inspector: taskInfo.value?.inspector, hasException: hasException.value, inspectionResult: hasException.value ? 0 : 1, commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem), commonFileListDTO: [], commonFileListAfterDTO: [], uploadedFiles: { before: beforeModelValue.value, after: [], issue: [], }, }; sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskData)); router.push("/equipmentManagement/repair/add"); }; 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 += 1; } return `${fileSize.toFixed(2)} ${units[index]}`; }; defineExpose({ openDialog, }); </script> <style scoped> .inspection-upload-page { min-height: 70vh; background: #f5f7fa; padding: 20px 20px 90px; box-sizing: border-box; } .inspection-upload-page { min-height: 70vh; background: #f5f7fa; padding: 20px 20px 90px; box-sizing: border-box; } .upload-content { max-width: 960px; margin: 20px auto 0; } .upload-content { max-width: 960px; margin: 20px auto 0; } .section-card { margin-bottom: 16px; } .section-card { margin-bottom: 16px; } .section-card h3 { margin: 0 0 16px; font-size: 16px; } .section-card h3 { margin: 0 0 16px; font-size: 16px; } .upload-buttons { display: flex; gap: 12px; margin: 16px 0; } .upload-buttons { display: flex; gap: 12px; margin: 16px 0; } .upload-progress { margin-bottom: 16px; } .upload-progress { margin-bottom: 16px; } .file-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; } .file-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; } .file-preview-container { position: relative; aspect-ratio: 1; border-radius: 8px; overflow: hidden; background: #f2f3f5; } .file-preview-container { position: relative; aspect-ratio: 1; border-radius: 8px; overflow: hidden; background: #f2f3f5; } .file-preview { width: 100%; height: 100%; } .file-preview { width: 100%; height: 100%; } .video-preview { width: 100%; height: 100%; background: #303133; color: #fff; display: flex; gap: 6px; align-items: center; justify-content: center; cursor: pointer; } .video-preview { width: 100%; height: 100%; background: #303133; color: #fff; display: flex; gap: 6px; align-items: center; justify-content: center; cursor: pointer; } .delete-btn { position: absolute; top: 6px; right: 6px; } .delete-btn { position: absolute; top: 6px; right: 6px; } .file-info { margin-top: 6px; font-size: 12px; } .file-info { margin-top: 6px; font-size: 12px; } .file-name { color: #606266; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .file-name { color: #606266; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .file-size { color: #909399; margin-top: 2px; } .file-size { color: #909399; margin-top: 2px; } .upload-summary { margin-top: 16px; } .upload-summary { margin-top: 16px; } .footer-buttons { position: sticky; left: 0; right: 0; bottom: 0; padding: 14px 20px 0; background: #f5f7fa; display: flex; justify-content: center; gap: 12px; } .footer-buttons { position: sticky; left: 0; right: 0; bottom: 0; padding: 14px 20px 0; background: #f5f7fa; display: flex; justify-content: center; gap: 12px; } .video-player { width: 100%; max-height: 70vh; background: #000; } .video-player { width: 100%; max-height: 70vh; background: #000; } </style> src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -1,37 +1,35 @@ <template> <div> <el-dialog title="查看附件" v-model="dialogVisitable" width="800px" @close="cancel"> <el-dialog title="查看附件" v-model="dialogVisitable" width="800px" @close="cancel"> <div class="upload-container"> <div class="form-container"> <div class="title">生产前</div> <div class="title">图片列表</div> <div class="media-list"> <img v-for="(item, index) in beforeProductionImgs" :key="`before-img-${index}`" :src="item" alt="" class="media-image" @click="showMedia(beforeProductionImgs, index, 'image')" /> <img v-for="(item, index) in beforeProductionImgs" :key="`before-img-${index}`" :src="item" alt="" class="media-image" @click="showMedia(beforeProductionImgs, index, 'image')" /> </div> <div class="media-list"> <div v-for="(videoUrl, index) in beforeProductionVideos" :key="`before-video-${index}`" class="video-item" @click="showMedia(beforeProductionVideos, index, 'video')" > <div v-for="(videoUrl, index) in beforeProductionVideos" :key="`before-video-${index}`" class="video-item" @click="showMedia(beforeProductionVideos, index, 'video')"> <div class="video-thumb"> <img src="@/assets/images/video.png" alt="播放" class="video-icon" /> <img src="@/assets/images/video.png" alt="播放" class="video-icon" /> </div> <div class="video-text">点击播放</div> </div> </div> </div> <div class="form-container"> <!-- <div class="form-container"> <div class="title">生产中</div> <div class="media-list"> @@ -87,22 +85,25 @@ <div class="video-text">点击播放</div> </div> </div> </div> </div> --> </div> </el-dialog> <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer"> <div class="media-viewer-content" @click.stop> <vue-easy-lightbox v-if="mediaType === 'image'" :visible="isMediaViewerVisible" :imgs="mediaList" :index="currentMediaIndex" @hide="closeMediaViewer" /> <div v-else-if="mediaType === 'video'" class="video-player-wrap"> <video :src="mediaList[currentMediaIndex]" autoplay controls class="video-player" /> <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer"> <div class="media-viewer-content" @click.stop> <vue-easy-lightbox v-if="mediaType === 'image'" :visible="isMediaViewerVisible" :imgs="mediaList" :index="currentMediaIndex" @hide="closeMediaViewer" /> <div v-else-if="mediaType === 'video'" class="video-player-wrap"> <video :src="mediaList[currentMediaIndex]" autoplay controls class="video-player" /> </div> </div> </div> @@ -110,215 +111,215 @@ </template> <script setup> import { ref } from "vue"; import VueEasyLightbox from "vue-easy-lightbox"; import { ref } from "vue"; import VueEasyLightbox from "vue-easy-lightbox"; const dialogVisitable = ref(false); const dialogVisitable = ref(false); const beforeProductionImgs = ref([]); const afterProductionImgs = ref([]); const productionIssuesImgs = ref([]); const beforeProductionImgs = ref([]); const afterProductionImgs = ref([]); const productionIssuesImgs = ref([]); const beforeProductionVideos = ref([]); const afterProductionVideos = ref([]); const productionIssuesVideos = ref([]); const beforeProductionVideos = ref([]); const afterProductionVideos = ref([]); const productionIssuesVideos = ref([]); const isMediaViewerVisible = ref(false); const currentMediaIndex = ref(0); const mediaList = ref([]); const mediaType = ref("image"); const isMediaViewerVisible = ref(false); const currentMediaIndex = ref(0); const mediaList = ref([]); const mediaType = ref("image"); const processFileUrl = fileUrl => { if (!fileUrl) return ""; const processFileUrl = fileUrl => { if (!fileUrl) return ""; let currentUrl = String(fileUrl); if (currentUrl.includes("\\")) { const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads"); if (uploadsIndex > -1) { currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`; } else { const fileName = currentUrl.split("\\").pop(); currentUrl = `/uploads/${fileName}`; let currentUrl = String(fileUrl); if (currentUrl.includes("\\")) { const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads"); if (uploadsIndex > -1) { currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`; } else { const fileName = currentUrl.split("\\").pop(); currentUrl = `/uploads/${fileName}`; } } } if (currentUrl && !currentUrl.startsWith("http")) { if (!currentUrl.startsWith("/")) { currentUrl = `/${currentUrl}`; if (currentUrl && !currentUrl.startsWith("http")) { if (!currentUrl.startsWith("/")) { currentUrl = `/${currentUrl}`; } currentUrl = __BASE_API__ + currentUrl; } currentUrl = __BASE_API__ + currentUrl; } return currentUrl; }; return currentUrl; }; const processItems = items => { const images = []; const videos = []; const processItems = items => { const images = []; const videos = []; if (!Array.isArray(items)) { if (!Array.isArray(items)) { return { images, videos }; } items.forEach(item => { if (!item) return; const fileUrl = processFileUrl( item.previewURL || item.url || item.downloadUrl || item.path || "" ); const contentType = String(item.contentType || "").toLowerCase(); if (!fileUrl) return; if (contentType.startsWith("video/")) { videos.push(fileUrl); return; } images.push(fileUrl); }); return { images, videos }; } }; items.forEach(item => { if (!item) return; const fileUrl = processFileUrl( item.previewURL || item.url || item.downloadUrl || item.path || "" const openDialog = row => { const { images: beforeImgs, videos: beforeVids } = processItems( row.commonFileListBeforeVO || [] ); const contentType = String(item.contentType || "").toLowerCase(); const { images: afterImgs, videos: afterVids } = processItems( row.commonFileListVO || [] ); const { images: issueImgs, videos: issueVids } = processItems( row.commonFileListAfterVO || [] ); if (!fileUrl) return; beforeProductionImgs.value = beforeImgs; beforeProductionVideos.value = beforeVids; afterProductionImgs.value = afterImgs; afterProductionVideos.value = afterVids; productionIssuesImgs.value = issueImgs; productionIssuesVideos.value = issueVids; dialogVisitable.value = true; }; if (contentType.startsWith("video/")) { videos.push(fileUrl); return; } const showMedia = (items, index, type) => { mediaList.value = items; currentMediaIndex.value = index; mediaType.value = type; isMediaViewerVisible.value = true; }; images.push(fileUrl); }); const closeMediaViewer = () => { isMediaViewerVisible.value = false; mediaList.value = []; mediaType.value = "image"; }; return { images, videos }; }; const cancel = () => { dialogVisitable.value = false; }; const openDialog = row => { const { images: beforeImgs, videos: beforeVids } = processItems( row.commonFileListBeforeVO || [] ); const { images: afterImgs, videos: afterVids } = processItems( row.commonFileListVO || [] ); const { images: issueImgs, videos: issueVids } = processItems( row.commonFileListAfterVO || [] ); beforeProductionImgs.value = beforeImgs; beforeProductionVideos.value = beforeVids; afterProductionImgs.value = afterImgs; afterProductionVideos.value = afterVids; productionIssuesImgs.value = issueImgs; productionIssuesVideos.value = issueVids; dialogVisitable.value = true; }; const showMedia = (items, index, type) => { mediaList.value = items; currentMediaIndex.value = index; mediaType.value = type; isMediaViewerVisible.value = true; }; const closeMediaViewer = () => { isMediaViewerVisible.value = false; mediaList.value = []; mediaType.value = "image"; }; const cancel = () => { dialogVisitable.value = false; }; defineExpose({ openDialog }); defineExpose({ openDialog }); </script> <style scoped lang="scss"> .upload-container { display: flex; flex-direction: column; align-items: center; padding: 20px; border: 1px solid #dcdfe6; box-sizing: border-box; .upload-container { display: flex; flex-direction: column; align-items: center; padding: 20px; border: 1px solid #dcdfe6; box-sizing: border-box; .form-container { flex: 1; width: 100%; margin-bottom: 20px; .form-container { flex: 1; width: 100%; margin-bottom: 20px; } } } .title { font-size: 14px; color: #165dff; line-height: 20px; font-weight: 600; padding-left: 10px; position: relative; margin: 6px 0; .title { font-size: 14px; color: #165dff; line-height: 20px; font-weight: 600; padding-left: 10px; position: relative; margin: 6px 0; &::before { content: ""; position: absolute; left: 0; top: 3px; width: 4px; height: 14px; background-color: #165dff; &::before { content: ""; position: absolute; left: 0; top: 3px; width: 4px; height: 14px; background-color: #165dff; } } } .media-list { display: flex; flex-wrap: wrap; } .media-list { display: flex; flex-wrap: wrap; } .media-image { max-width: 100px; height: 100px; margin: 5px; cursor: pointer; } .media-image { max-width: 100px; height: 100px; margin: 5px; cursor: pointer; } .video-item { position: relative; margin: 10px; cursor: pointer; } .video-item { position: relative; margin: 10px; cursor: pointer; } .video-thumb { width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center; } .video-thumb { width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center; } .video-icon { width: 30px; height: 30px; opacity: 0.8; } .video-icon { width: 30px; height: 30px; opacity: 0.8; } .video-text { text-align: center; font-size: 12px; color: #666; } .video-text { text-align: center; font-size: 12px; color: #666; } .media-viewer-overlay { position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.8); z-index: 9999; display: flex; align-items: center; justify-content: center; } .media-viewer-overlay { position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.8); z-index: 9999; display: flex; align-items: center; justify-content: center; } .media-viewer-content { position: relative; max-width: 90vw; max-height: 90vh; overflow: hidden; } .media-viewer-content { position: relative; max-width: 90vw; max-height: 90vh; overflow: hidden; } .video-player-wrap { position: relative; } .video-player-wrap { position: relative; } .video-player { max-width: 90vw; max-height: 80vh; } .video-player { max-width: 90vw; max-height: 80vh; } </style> src/views/equipmentManagement/inspectionManagement/index.vue
@@ -231,6 +231,12 @@ operation: operations .map(op => { switch (op) { case "view": return { name: "详情", clickFun: openViewDialog, color: "#409EFF", }; case "edit": return { name: "编辑", @@ -273,14 +279,14 @@ ]; operationsArr.value = ["edit"]; } else if (value === "task") { const operationColumn = getOperationColumn(["upload", "viewFile"]); const operationColumn = getOperationColumn(["view", "upload", "viewFile"]); // 巡检记录不展示"是否启用"列 const taskColumns = columns.value.filter(col => col.prop !== "isEnabled"); tableColumns.value = [ ...taskColumns, ...(operationColumn ? [operationColumn] : []), ]; operationsArr.value = ["upload", "viewFile"]; operationsArr.value = ["view", "upload", "viewFile"]; } pageNum.value = 1; pageSize.value = 10; @@ -377,7 +383,13 @@ const openUploadDialog = row => { nextTick(() => { uploadFiles.value?.openDialog(row); uploadFiles.value?.openDialog("add", row); }); }; const openViewDialog = row => { nextTick(() => { uploadFiles.value?.openDialog("view", row); }); }; src/views/equipmentManagement/upkeep/Form/formDia.vue
@@ -101,7 +101,7 @@ <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType"> <el-form-item label="日期" prop="frequencyDetail"> <el-time-picker v-model="form.frequencyDetail" placeholder="选择时间" format="HH:mm" value-format="HH:mm" /> value-format="HH:mm" /> </el-form-item> </el-col> <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType"> @@ -116,7 +116,7 @@ <el-option label="周日" value="SUN"/> </el-select> <el-time-picker v-model="form.time" placeholder="选择时间" format="HH:mm" value-format="HH:mm" style="width: 50%"/> value-format="HH:mm" style="width: 50%"/> </el-form-item> </el-col> <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType"> @@ -141,6 +141,23 @@ format="MM,DD,HH:mm" value-format="MM,DD,HH:mm" /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="定时任务"> <el-switch v-model="form.isActive" :active-value="1" :inactive-value="0" active-text="开启" inactive-text="关闭" inline-prompt /> <span style="margin-left: 12px; color: #909399; font-size: 12px;"> {{ form.isActive === 1 ? '任务将按计划执行' : '任务暂停执行' }} </span> </el-form-item> </el-col> </el-row> @@ -184,7 +201,8 @@ time: '', deviceModel: undefined, // 规格型号 registrationDate: '', maintenancePerson: '' // 保养人 maintenancePerson: '', // 保养人 isActive: 0 // 定时任务开关:0=关闭,1=开启 }, rules: { taskId: [{ required: true, message: "请选择设备", trigger: "change" },], @@ -271,7 +289,8 @@ time: '', deviceModel: undefined, registrationDate: '', maintenancePerson: '' maintenancePerson: '', isActive: 0 } } @@ -294,21 +313,21 @@ } delete payload.inspector delete payload.inspectorIds delete payload.active if (payload.frequencyType === 'WEEKLY') { let frequencyDetail = '' frequencyDetail = payload.week + ',' + payload.time payload.frequencyDetail = frequencyDetail } // 录入日期:直接使用表单里的 registrationDate 字段 // 一些默认状态字段 if (payload.status === undefined || payload.status === null || payload.status === '') { payload.status = '0' // 默认状态,可按实际枚举调整 } payload.active = true payload.deleted = 0 if (operationType.value === 'edit') { await deviceMaintenanceTaskEdit(payload) } else { src/views/equipmentManagement/upkeep/index.vue
@@ -63,6 +63,12 @@ }" @selection-change="handleScheduledSelectionChange" @pagination="changeScheduledPage"> <template #isActiveRef="{ row }"> <el-tag v-if="row.isActive === 1" type="success">开启</el-tag> <el-tag v-else type="info">关闭</el-tag> </template> <template #statusRef="{ row }"> <el-tag v-if="row.status === 1" type="success">启用</el-tag> @@ -308,7 +314,7 @@ { prop: "frequencyType", label: "频次", minWidth: 150, minWidth: 50, // PIMTable 使用的是 formatData,而不是 Element-Plus 的 formatter formatData: cell => ({ @@ -321,7 +327,7 @@ { prop: "frequencyDetail", label: "开始日期与时间", minWidth: 150, minWidth: 130, // 同样改用 formatData,PIMTable 内部会把单元格值传进来 formatData: cell => { if (typeof cell !== "string") return ""; @@ -342,6 +348,14 @@ ); }, }, { prop: "isActive", label: "定时任务", dataType: "slot", slot: "isActiveRef", align: "center", minWidth: 100, }, { prop: "maintenancePerson", label: "保养人", minWidth: 100 }, { prop: "registrant", label: "登记人", minWidth: 100 }, { @@ -349,7 +363,7 @@ label: "登记日期", minWidth: 100, formatData: cell => cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-", cell ? dayjs(cell).format("YYYY-MM-DD") : "-", }, { fixed: "right", @@ -357,7 +371,7 @@ dataType: "slot", slot: "operation", align: "center", width: "200px", width: "150px", }, ]); src/views/procurementManagement/procurementLedger/detail.vue
@@ -7,7 +7,7 @@ <span class="readonly-text">{{ form.purchaseContractNumber || '--' }}</span> </el-form-item> </el-col> <el-col :span="12"> <el-col v-if="showSalesContractBinding" :span="12"> <el-form-item label="销售合同号:"> <span class="readonly-text">{{ form.salesContractNo || '--' }}</span> </el-form-item> @@ -129,6 +129,8 @@ import { getPurchaseById } from "@/api/procurementManagement/procurementLedger" const visible = ref(false) // 是否显示销售合同号绑定 const showSalesContractBinding = false const form = ref({}) const productData = ref([]) const fileList = ref([]) src/views/procurementManagement/procurementLedger/index.vue
@@ -11,7 +11,7 @@ <el-input v-model="searchForm.purchaseContractNumber" style="width: 240px" placeholder="请输入" @change="handleQuery" clearable :prefix-icon="Search" /> </el-form-item> <el-form-item label="销售合同号:"> <el-form-item v-if="showSalesContractBinding" label="销售合同号:"> <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search" @change="handleQuery" /> </el-form-item> @@ -76,7 +76,7 @@ </el-table-column> <el-table-column align="center" label="序号" type="index" width="60" /> <el-table-column label="采购合同号" prop="purchaseContractNumber" width="160" show-overflow-tooltip /> <el-table-column label="销售合同号" prop="salesContractNo" width="160" show-overflow-tooltip /> <el-table-column v-if="showSalesContractBinding" label="销售合同号" prop="salesContractNo" width="160" show-overflow-tooltip /> <el-table-column label="供应商名称" prop="supplierName" width="160" show-overflow-tooltip /> <el-table-column label="项目名称" prop="projectName" width="320" show-overflow-tooltip /> <el-table-column label="审批状态" prop="approvalStatus" width="100" show-overflow-tooltip> @@ -124,7 +124,7 @@ <el-input v-model="form.purchaseContractNumber" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-col v-if="showSalesContractBinding" :span="12"> <el-form-item label="销售合同号:" prop="salesLedgerId"> <el-select v-model="form.salesLedgerId" placeholder="请选择" filterable clearable @change="salesLedgerChange"> <el-option v-for="item in salesContractList" :key="item.id" :label="item.salesContractNo" :value="item.id" /> @@ -439,6 +439,9 @@ import FileUpload from "@/components/AttachmentUpload/file/index.vue"; const userStore = useUserStore(); // 是否显示销售合同号绑定 const showSalesContractBinding = false; // 订单审批状态显示文本 const approvalStatusText = { @@ -1034,7 +1037,6 @@ if (form.value.salesLedgerId == -1) { form.value.salesLedgerId = null; } console.log(form.value, "form.value==========="); dialogFormVisible.value = true; } catch (error) { console.error("打开表单失败:", error); @@ -1536,9 +1538,7 @@ }; // 销售合同选择改变方法 const salesLedgerChange = async row => { console.log("row", row); var index = salesContractList.value.findIndex(item => item.id == row); console.log("index", index); if (index > -1) { await querygProductInfoByContractNo(); } src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -102,12 +102,8 @@ </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="检测结果:" prop="checkResult"> <el-select v-model="form.checkResult" :disabled="isViewMode"> <el-option label="合格" value="合格" /> <el-option label="不合格" value="不合格" /> <el-option label="部分合格" value="部分合格" /> </el-select> <el-form-item label="合格率:"> <el-tag :type="passRateTagType">{{ passRateDisplayText }}</el-tag> </el-form-item> </el-col> </el-row> @@ -188,7 +184,6 @@ qualifiedQuantity: "", unqualifiedQuantity: "", checkCompany: "", checkResult: "", }, rules: { checkTime: [{ required: true, message: "请输入", trigger: "blur" }], @@ -202,12 +197,42 @@ qualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }], unqualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }], checkCompany: [{ required: false, message: "请输入", trigger: "blur" }], checkResult: [{ required: true, message: "请输入", trigger: "change" }], }, }); const { form, rules } = toRefs(data); // 是否为查看模式 const isViewMode = computed(() => operationType.value === 'view'); const passRateValue = computed(() => { const fromApi = form.value.passRate; if (fromApi != null && fromApi !== '') { const n = Number(fromApi); if (!Number.isNaN(n)) return n; } const quantity = Number(form.value.quantity); const qualified = Number(form.value.qualifiedQuantity); if (!quantity || Number.isNaN(quantity)) return null; const qualifiedNum = Number.isNaN(qualified) ? 0 : qualified; return (qualifiedNum / quantity) * 100; }); const passRateDisplayText = computed(() => { const params = passRateValue.value; if (params == null || params === '') return '—'; const n = Number(params); if (Number.isNaN(n)) return '—'; return `${n.toFixed(2)}%`; }); const passRateTagType = computed(() => { const params = passRateValue.value; if (params == null || params === '') return 'info'; const n = Number(params); if (Number.isNaN(n)) return 'info'; if (n >= 100) return 'success'; if (n >= 90) return 'warning'; return 'danger'; }); // 编辑时:productMainId 或 purchaseLedgerId 任一有值则工序、数量置灰 const processQuantityDisabled = computed(() => { const v = form.value || {}; src/views/qualityManagement/finalInspection/index.vue
@@ -143,22 +143,31 @@ width: 120 }, { label: "检测结果", prop: "checkResult", label: "合格率", prop: "passRate", width: 100, dataType: "tag", formatData: (params) => { if (params == null || params === '') return '—'; const n = Number(params); if (Number.isNaN(n)) return '—'; return `${n.toFixed(2)}%`; }, formatType: (params) => { if (params == '不合格') { return "danger"; } else if (params == '合格') { return "success"; } else { return 'danger'; } if (params == null || params === '') return 'info'; const n = Number(params); if (Number.isNaN(n)) return 'info'; if (n === 100) return 'success'; if (n >= 75) return 'primary'; if (n >= 50) return 'warning'; if (n >= 25) return 'danger'; return 'danger'; }, }, { label: "提交状态", prop: "inspectState", dataType: "tag", formatData: (params) => { if (params) { return "已提交"; @@ -166,6 +175,13 @@ return "未提交"; } }, formatType: (params) => { if (params) { return "success"; } else { return "info"; } }, }, { dataType: "action", src/views/qualityManagement/processInspection/components/formDia.vue
@@ -139,16 +139,8 @@ </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="检测结果:" prop="checkResult"> <el-select v-model="form.checkResult" :disabled="isViewMode"> <el-option label="合格" value="合格" /> <el-option label="不合格" value="不合格" /> <el-option label="部分合格" value="部分合格" /> </el-select> <el-form-item label="合格率:"> <el-tag :type="passRateTagType">{{ passRateDisplayText }}</el-tag> </el-form-item> </el-col> </el-row> @@ -250,7 +242,6 @@ qualifiedQuantity: "", unqualifiedQuantity: "", checkCompany: "", checkResult: "", }, rules: { checkTime: [{ required: true, message: "请输入", trigger: "blur" }], @@ -264,13 +255,43 @@ qualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }], unqualifiedQuantity: [{ required: true, message: "请输入", trigger: "blur" }], checkCompany: [{ required: false, message: "请输入", trigger: "blur" }], checkResult: [{ required: true, message: "请输入", trigger: "change" }], }, }); const userList = ref([]); const { form, rules } = toRefs(data); // 是否为查看模式 const isViewMode = computed(() => operationType.value === 'view'); const passRateValue = computed(() => { const fromApi = form.value.passRate; if (fromApi != null && fromApi !== '') { const n = Number(fromApi); if (!Number.isNaN(n)) return n; } const quantity = Number(form.value.quantity); const qualified = Number(form.value.qualifiedQuantity); if (!quantity || Number.isNaN(quantity)) return null; const qualifiedNum = Number.isNaN(qualified) ? 0 : qualified; return (qualifiedNum / quantity) * 100; }); const passRateDisplayText = computed(() => { const params = passRateValue.value; if (params == null || params === '') return '—'; const n = Number(params); if (Number.isNaN(n)) return '—'; return `${n.toFixed(2)}%`; }); const passRateTagType = computed(() => { const params = passRateValue.value; if (params == null || params === '') return 'info'; const n = Number(params); if (Number.isNaN(n)) return 'info'; if (n >= 100) return 'success'; if (n >= 90) return 'warning'; return 'danger'; }); // 编辑时:productMainId 或 purchaseLedgerId 任一有值则工序、数量置灰 const processQuantityDisabled = computed(() => { const v = form.value || {}; @@ -338,7 +359,6 @@ unit: "", quantity: "", checkCompany: "", checkResult: "", }; testStandardOptions.value = []; tableData.value = []; src/views/qualityManagement/processInspection/index.vue
@@ -142,22 +142,31 @@ width: 120 }, { label: "检测结果", prop: "checkResult", label: "合格率", prop: "passRate", width: 100, dataType: "tag", formatData: (params) => { if (params == null || params === '') return '—'; const n = Number(params); if (Number.isNaN(n)) return '—'; return `${n.toFixed(2)}%`; }, formatType: (params) => { if (params == '不合格') { return "danger"; } else if (params == '合格') { return "success"; } else { return 'danger'; } if (params == null || params === '') return 'info'; const n = Number(params); if (Number.isNaN(n)) return 'info'; if (n === 100) return 'success'; if (n >= 75) return 'primary'; if (n >= 50) return 'warning'; if (n >= 25) return 'danger'; return 'danger'; }, }, { label: "提交状态", prop: "inspectState", dataType: "tag", formatData: (params) => { if (params) { return "已提交"; @@ -165,6 +174,13 @@ return "未提交"; } }, formatType: (params) => { if (params) { return "success"; } else { return "info"; } }, }, { dataType: "action", src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -107,12 +107,8 @@ </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="检测结果:" prop="checkResult"> <el-select v-model="form.checkResult" :disabled="isViewMode"> <el-option label="合格" value="合格"/> <el-option label="不合格" value="不合格"/> <el-option label="部分合格" value="部分合格"/> </el-select> <el-form-item label="合格率:"> <el-tag :type="passRateTagType">{{ passRateDisplayText }}</el-tag> </el-form-item> </el-col> </el-row> @@ -194,7 +190,6 @@ unit: "", quantity: "", checkCompany: "", checkResult: "", }, rules: { checkTime: [{required: true, message: "请输入", trigger: "blur"},], @@ -208,7 +203,6 @@ qualifiedQuantity: [{required: true, message: "请输入", trigger: "blur"}], unqualifiedQuantity: [{required: true, message: "请输入", trigger: "blur"}], checkCompany: [{required: false, message: "请输入", trigger: "blur"}], checkResult: [{required: true, message: "请选择检测结果", trigger: "change"}], }, }); const tableColumn = ref([ @@ -249,6 +243,37 @@ // 是否为查看模式 const isViewMode = computed(() => operationType.value === 'view'); const passRateValue = computed(() => { const fromApi = form.value.passRate; if (fromApi != null && fromApi !== '') { const n = Number(fromApi); if (!Number.isNaN(n)) return n; } const quantity = Number(form.value.quantity); const qualified = Number(form.value.qualifiedQuantity); if (!quantity || Number.isNaN(quantity)) return null; const qualifiedNum = Number.isNaN(qualified) ? 0 : qualified; return (qualifiedNum / quantity) * 100; }); const passRateDisplayText = computed(() => { const params = passRateValue.value; if (params == null || params === '') return '—'; const n = Number(params); if (Number.isNaN(n)) return '—'; return `${n.toFixed(2)}%`; }); const passRateTagType = computed(() => { const params = passRateValue.value; if (params == null || params === '') return 'info'; const n = Number(params); if (Number.isNaN(n)) return 'info'; if (n >= 100) return 'success'; if (n >= 90) return 'warning'; return 'danger'; }); // 编辑时:productMainId 或 purchaseLedgerId 任一有值则供应商、数量置灰 const supplierQuantityDisabled = computed(() => { const v = form.value || {}; @@ -282,7 +307,6 @@ unit: "", quantity: "", checkCompany: "", checkResult: "", } testStandardOptions.value = []; tableData.value = []; src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -144,27 +144,31 @@ width: 120 }, { label: "检测单位", prop: "checkCompany", width: 120 }, { label: "检测结果", prop: "checkResult", label: "合格率", prop: "passRate", width: 100, dataType: "tag", formatData: (params) => { if (params == null || params === '') return '—'; const n = Number(params); if (Number.isNaN(n)) return '—'; return `${n.toFixed(2)}%`; }, formatType: (params) => { if (params === '不合格') { return "danger"; } else if (params === '合格') { return "success"; } else { return 'danger'; } if (params == null || params === '') return 'info'; const n = Number(params); if (Number.isNaN(n)) return 'info'; if (n === 100) return 'success'; if (n >= 75) return 'primary'; if (n >= 50) return 'warning'; if (n >= 25) return 'danger'; return 'danger'; }, }, { label: "提交状态", prop: "inspectState", dataType: "tag", formatData: (params) => { if (params) { return "已提交"; @@ -172,6 +176,13 @@ return "未提交"; } }, formatType: (params) => { if (params) { return "success"; } else { return "info"; } }, }, { dataType: "action",