From 3ed318530afb4c74c3bb5be04155151bc57444fe Mon Sep 17 00:00:00 2001 From: caozehui <2427765068@qq.com> Date: Wed, 15 Jul 2026 15:57:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=AD=98=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.env | 2 +- frontend/src/api/check/interface/index.ts | 19 +++ frontend/src/api/plan/interface/index.ts | 1 + frontend/src/api/plan/planData.ts | 2 +- frontend/src/api/plan/static.json | 2 +- frontend/src/api/socket/socket.ts | 9 ++ frontend/src/stores/modules/check.ts | 14 +- frontend/src/views/home/components/table.vue | 32 +++- frontend/src/views/home/components/test.vue | 125 ++++++++++++-- .../src/views/home/components/testPopup.vue | 153 ++++++++++++++++-- .../views/home/components/writeTHPopup.vue | 23 ++- .../plan/planList/components/childrenPlan.vue | 2 +- .../components/planStatisticsPopup.vue | 15 +- 13 files changed, 350 insertions(+), 49 deletions(-) diff --git a/frontend/.env b/frontend/.env index 5faf502..cfba239 100644 --- a/frontend/.env +++ b/frontend/.env @@ -18,4 +18,4 @@ VITE_COMPANY_NAME=南京灿能电力自动化股份有限公司 VITE_IS_SHOW_RAW_DATA=true # 开启激活验证 -VITE_ACTIVATE_OPEN=true +VITE_ACTIVATE_OPEN=false diff --git a/frontend/src/api/check/interface/index.ts b/frontend/src/api/check/interface/index.ts index 4932b23..f48eed8 100644 --- a/frontend/src/api/check/interface/index.ts +++ b/frontend/src/api/check/interface/index.ts @@ -134,6 +134,8 @@ export namespace CheckData { devCurr: number //设备电流 factorFlag: number //是否支持系数校准 checkResult: number //检测结果 + checkState?: number | null //检测状态 + formalCheckTime?: number | null //正式检测耗时 chnNumList: string[] //连线存储数据 } @@ -179,10 +181,27 @@ export namespace CheckData { scriptType: string scriptName?: string //可以不要该属性,有点多余 code?: string + scriptCode?: string // 设备 devices: Array } + export interface FormalProgressParam { + planId: string + devIds: string[] + userPageId: string + userId?: string + formalCheckTime?: number + } + + export interface FormalProgressVO { + formalCheckTime: number + minMaxSort: number | null + nextSort: number | null + process: number + tableRows: ScriptChnItem[] + } + export enum ButtonColorEnum { INFO = '#909399', LOADING = 'var(--el-color-primary)', diff --git a/frontend/src/api/plan/interface/index.ts b/frontend/src/api/plan/interface/index.ts index ec2f790..47d284c 100644 --- a/frontend/src/api/plan/interface/index.ts +++ b/frontend/src/api/plan/interface/index.ts @@ -86,6 +86,7 @@ export namespace Plan { totalCheckCount: number; checkedDeviceCount: number; uncheckedDeviceCount: number; + checkingDeviceCount: number; firstQualifiedDeviceCount: number; secondQualifiedDeviceCount: number; thirdOrMoreQualifiedDeviceCount: number; diff --git a/frontend/src/api/plan/planData.ts b/frontend/src/api/plan/planData.ts index 6b457fd..31fdfd5 100644 --- a/frontend/src/api/plan/planData.ts +++ b/frontend/src/api/plan/planData.ts @@ -486,4 +486,4 @@ const planData = ref([ // }, // ]) -export {planData,dictPattern,dictTestState,dictReportState,dictResult,testPlanDataList,sourceDataList,deviceDataList,testSoureDataList,testScriptDataList,testErrSystDataList,testFatherPlanList} \ No newline at end of file +export {planData,dictPattern,dictTestState,dictReportState,dictResult,testPlanDataList,sourceDataList,deviceDataList,testSoureDataList,testScriptDataList,testErrSystDataList,testFatherPlanList} diff --git a/frontend/src/api/plan/static.json b/frontend/src/api/plan/static.json index 298f8da..971eb65 100644 --- a/frontend/src/api/plan/static.json +++ b/frontend/src/api/plan/static.json @@ -60,4 +60,4 @@ "id": 3 } ] -} \ No newline at end of file +} diff --git a/frontend/src/api/socket/socket.ts b/frontend/src/api/socket/socket.ts index b48f404..d89ceef 100644 --- a/frontend/src/api/socket/socket.ts +++ b/frontend/src/api/socket/socket.ts @@ -1,5 +1,6 @@ import http from '@/api' import { useDetectionLockStore } from '@/stores/modules/detectionLock' +import type { CheckData } from '@/api/check/interface' export const startPreTest = async (params) => { @@ -37,6 +38,14 @@ export const resumeTest = (params) => { return http.post(`/prepare/restartTemTest/`, params, {loading: false}) } +export const saveFormalProgress = (params: CheckData.FormalProgressParam & { formalCheckTime: number }) => { + return http.post(`/prepare/saveFormalProgress`, params, { loading: false }) +} + +export const getFormalProgress = (params: CheckData.FormalProgressParam) => { + return http.post(`/prepare/formalProgress`, params, { loading: false }) +} + /** * 比对式通道配对 * @param params diff --git a/frontend/src/stores/modules/check.ts b/frontend/src/stores/modules/check.ts index c1dd0e8..f0540ec 100644 --- a/frontend/src/stores/modules/check.ts +++ b/frontend/src/stores/modules/check.ts @@ -20,7 +20,10 @@ export const useCheckStore = defineStore(CHECK_STORE_KEY, { temperature: 0, humidity: 0, chnNumList: [] as string[], //连线数据 - nodesConnectable: true //设置是能可以连线 + nodesConnectable: true, //设置是能可以连线 + resumeFormal: false, + restoringFormalProgress: false, + formalProgressSnapshot: null as CheckData.FormalProgressVO | null }), getters: {}, actions: { @@ -67,6 +70,15 @@ export const useCheckStore = defineStore(CHECK_STORE_KEY, { }, setNodesConnectable(nodesConnectable: boolean) { this.nodesConnectable = nodesConnectable + }, + setResumeFormal(resumeFormal: boolean) { + this.resumeFormal = resumeFormal + }, + setRestoringFormalProgress(restoring: boolean) { + this.restoringFormalProgress = restoring + }, + setFormalProgressSnapshot(snapshot: CheckData.FormalProgressVO | null) { + this.formalProgressSnapshot = snapshot } } }) diff --git a/frontend/src/views/home/components/table.vue b/frontend/src/views/home/components/table.vue index 6efd8fe..c71b07e 100644 --- a/frontend/src/views/home/components/table.vue +++ b/frontend/src/views/home/components/table.vue @@ -483,7 +483,11 @@ const columns = reactive[]>([ sortable: true, isShow: checkStateShow, render: scope => { - return scope.row.checkState === 0 ? '未检' : scope.row.checkState === 1 ? '检测中' : scope.row.checkState === 2 ? '检测完成':'归档' + return scope.row.checkState === 0 ? '未检' + : scope.row.checkState === 1 ? '检测中' + : scope.row.checkState === 2 ? '检测完成' + : scope.row.checkState === 3 ? '归档' + : '' } }, { @@ -586,7 +590,9 @@ const handleSelectionChange = (selection: any[]) => { devVolt: item.devVolt, devCurr: item.devCurr, factorFlag: item.factorFlag, - checkResult: item.checkResult + checkResult: item.checkResult, + checkState: item.checkState, + formalCheckTime: item.formalCheckTime } }) @@ -600,14 +606,16 @@ const handleSelectionChange = (selection: any[]) => { const isUncheckedDevice = (device: Device.ResPqDev) => Number(device.checkState) === 0 || Number(device.checkResult) === 2 -const hasCheckedSelectedDevice = () => channelsSelection.value.some(device => !isUncheckedDevice(device)) - const hasUncheckedSelectedDevice = () => channelsSelection.value.some(device => isUncheckedDevice(device)) const hasCheckedUnqualifiedSelectedDevice = () => channelsSelection.value.some(device => !isUncheckedDevice(device) && Number(device.checkResult) === 0) -const shouldShowRecheckModeDialog = () => hasCheckedSelectedDevice() +const isCheckedUnqualifiedDevice = (device: Device.ResPqDev) => + Number(device.checkState) === 2 && Number(device.checkResult) === 0 + +const shouldShowRecheckModeDialog = () => + channelsSelection.value.length > 0 && channelsSelection.value.every(isCheckedUnqualifiedDevice) const canUseUnqualifiedItemRecheck = () => hasCheckedUnqualifiedSelectedDevice() && !hasUncheckedSelectedDevice() @@ -653,7 +661,7 @@ function tableHeaderInit(val: number) { switch (val) { case 0: // 设备检测模式 case 1: // 手动检测模式 - checkStateTable.value = [0, 1, 2] // 显示所有检测状态 + checkStateTable.value = [0, 1, 2] // 显示未检、检测中、检测完成状态 tableKey.value++ operationShow.value = false // 隐藏操作列 documentStateShow.value = false // 隐藏文档状态 @@ -665,7 +673,7 @@ function tableHeaderInit(val: number) { break case 3: // 报告生成模式 if (modeStore.currentMode === '比对式') { - checkStateTable.value = [2, 3] // 显示检测中,检测完成和归档状态 + checkStateTable.value = [2, 3] // 显示检测完成和归档状态 } else { checkStateTable.value = [2, 3] // 显示检测完成和归档状态 } @@ -689,7 +697,7 @@ function tableHeaderInit(val: number) { break case 5: // 数据查询模式 if (modeStore.currentMode === '比对式') { - checkStateTable.value = [1, 2, 3] // 显示检测中,检测完成和归档状态 + checkStateTable.value = [1, 2, 3] // 显示检测中、检测完成和归档状态 } else { checkStateTable.value = [2, 3] // 显示检测完成和归档状态 } @@ -914,6 +922,14 @@ const handleTest = async (val: string) => { // 处理检测相关操作 if (val === '手动检测' || val === '一键检测' || val === '系数校准') { + const hasCheckingDevice = channelsSelection.value.some(item => Number(item.checkState) === 1) + const hasNonCheckingDevice = channelsSelection.value.some(item => Number(item.checkState) !== 1) + if (hasCheckingDevice && hasNonCheckingDevice) { + ElMessage.warning('检测中的设备不能和未检、检测完成的设备同时检测') + return + } + checkStore.setResumeFormal(false) + // 检查选中设备的检测状态是否一致 const checkStates = channelsSelection.value.map(item => item.checkState) const allCheckStatesEqual = new Set(checkStates).size <= 1 diff --git a/frontend/src/views/home/components/test.vue b/frontend/src/views/home/components/test.vue index 97e75e1..65e52bf 100644 --- a/frontend/src/views/home/components/test.vue +++ b/frontend/src/views/home/components/test.vue @@ -105,7 +105,7 @@ import { InfoFilled, Loading } from '@element-plus/icons-vue' // 单通道单测试项详情弹窗组件 import dataCheckSingleChannelSingleTestPopup from './dataCheckSingleChannelSingleTestPopup.vue' // Vue 3 Composition API -import { computed, onBeforeUnmount, onMounted, reactive, ref, toRef, watch } from 'vue' +import { computed, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, toRef, watch } from 'vue' // 对话框大小绑定工具 import { dialogBig } from '@/utils/elementBind' // 检测数据类型定义 @@ -151,7 +151,14 @@ const props = defineProps({ }) // 定义向父组件发送的事件 -const emit = defineEmits(['update:testStatus', 'update:webMsgSend', 'sendPause', 'sendResume', 'closeWebSocket']) +const emit = defineEmits([ + 'update:testStatus', + 'update:webMsgSend', + 'sendPause', + 'sendResume', + 'closeWebSocket', + 'restoreFormalProgressRequired' +]) // ========== 界面状态相关 ========== // 测试项进度抽屉是否打开 @@ -284,13 +291,6 @@ watch(testStatus, function(newValue, oldValue) { // 直接进入检测流程 emit('update:testStatus', 'process') } - // 开始计时 - startTimeCount() - // 显示测试日志抽屉 - showTestLog() - // 重置时间记录 - startData.value = new Date() - timeDifference.value = 0 } // 需要停止计时的状态 @@ -552,14 +552,8 @@ watch(webMsgSend, function(newValue, oldValue) { switch (newValue.operateCode) { case 'VERIFY_MAPPING$01': if (newValue.code == 25001) { - ElMessage.success('初始化成功!') testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:协议校验成功!` }) - timeDifference.value = +new Date().getTime() - startData.value.getTime() - testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:初始化成功!` }) percentage.value = 3 - // 初始化完成,准备开始正式检测 - activeIndex = getNextActiveIndex() + 2 - emit('update:testStatus', 'process') } else if (newValue.code == 25002) { let data = JSON.parse(newValue.data) ElMessageBox.alert(`脚本与icd校验失败!icd名称:${data['icdType']} -> 校验项:${data['dataType']}`, '初始化失败', { @@ -585,6 +579,62 @@ watch(webMsgSend, function(newValue, oldValue) { break } break + case 'YJC_xujy': + switch (newValue.operateCode) { + case 'OPER_GATHER': + if (newValue.code == 10200) { + testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:相序校验源参数下发成功!` }) + } else if (newValue.code == 10201) { + testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:相序校验源参数下发中...` }) + } else if (newValue.code == 10520) { + ElMessageBox.alert('相序校验报文解析异常!', '初始化失败', { + confirmButtonText: '确定', + type: 'error', + }) + testLogList.push({ type: 'error', log: `${new Date().toLocaleString()}:相序校验报文解析异常!` }) + emit('update:testStatus', 'test_init_fail') + } else if (newValue.code == -1) { + ElMessageBox.alert('相序校验源未知异常!', '初始化失败', { + confirmButtonText: '确定', + type: 'error', + }) + testLogList.push({ type: 'error', log: `${new Date().toLocaleString()}:相序校验源未知异常!` }) + emit('update:testStatus', 'test_init_fail') + } + break + case 'DATA_REQUEST$02': + if (newValue.code == 10200) { + testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:${newValue.data}` }) + } else if (newValue.code == 10201) { + testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:获取相序校验数据中...` }) + } else if (newValue.code == 25003) { + ElMessageBox.alert('相序校验未通过!', '初始化失败', { + confirmButtonText: '确定', + type: 'error', + }) + testLogList.push({ type: 'error', log: `${new Date().toLocaleString()}:相序校验未通过!` }) + emit('update:testStatus', 'test_init_fail') + } else if (newValue.code == 25001) { + ElMessage.success('初始化成功!') + testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:相序校验成功!` }) + timeDifference.value = +new Date().getTime() - startData.value.getTime() + testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:初始化成功!` }) + percentage.value = 4 + // 初始化完成,准备开始正式检测 + activeIndex = getNextActiveIndex() + 2 + emit('update:testStatus', 'process') + } + break + } + break + case 'formal_progress_required': + testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:恢复进度中` }) + emit('restoreFormalProgressRequired') + break + case 'formal_progress_no_next': + ElMessage.warning('当前暂存进度没有可续检脚本,请重新开始检测') + emit('update:testStatus', 'error') + break // 暂停测试成功 case 'preStopTest': if (newValue.operateCode == 'stop') { @@ -698,6 +748,9 @@ watch(webMsgSend, function(newValue, oldValue) { // ========== 检测项处理函数 ========== // 处理检测项开始消息 const handleStartItem = (code: string, desc: string | undefined) => { + if (!timer) { + startTimeCount() + } if (desc === undefined) { // 大测试项开始 activeIndex = getActiveIndex(code) @@ -962,7 +1015,7 @@ const updateLog = (isStart: boolean) => { } // 更新总时间显示 - timeView.value = secondToTime(timeDifference.value / 1000) + // timeView is driven by timeCount so resumed formal tests keep base + current elapsed time. // 如果是最后一个测试项,记录总结日志 if (activeIndex === checkTotal) { testLogList.push({ @@ -1208,6 +1261,13 @@ const startTimeCount = () => { } } +const startFormalTimer = () => { + startTimeCount() + showTestLog() + startData.value = new Date() + timeDifference.value = 0 +} + // 停止计时 const stopTimeCount = () => { if (timer) { @@ -1243,6 +1303,36 @@ const secondToTime = (second: number) => { return h + ':' + m + ':' + s } +const getElapsedSeconds = () => { + return timeCount.value +} + +const applyFormalProgress = (snapshot: CheckData.FormalProgressVO) => { + stopTimeCount() + timeCount.value = snapshot.formalCheckTime ?? 0 + timeView.value = secondToTime(timeCount.value) + timeDifference.value = timeCount.value * 1000 + percentage.value = Math.trunc(snapshot.process ?? 0) + + checkResult.splice(0) + snapshot.tableRows.forEach(row => { + const scriptItem = scriptData.find(item => item.code === row.code || item.code === row.scriptCode || item.id === row.scriptType) + checkResult.push({ + scriptType: scriptItem?.id ?? row.scriptType, + scriptName: scriptItem?.scriptName ?? row.scriptName, + code: row.code ?? row.scriptCode ?? scriptItem?.code, + devices: row.devices.map(device => ({ + deviceId: device.deviceId, + deviceName: device.deviceName, + chnResult: [...device.chnResult] + })) + }) + }) + activeIndex = snapshot.tableRows.filter(row => + row.devices.every(device => device.chnResult.every(result => result !== CheckData.ChnCheckResultEnum.UNKNOWN)) + ).length +} + onMounted(() => { mittBus.on(STOP_DETECTION_TIMER_EVENT, handleStopDetectionTimer) }) @@ -1273,6 +1363,9 @@ onBeforeUnmount(() => { // 暴露给父组件的方法 defineExpose({ handlePause, // 暂停方法 + getElapsedSeconds, + applyFormalProgress, + startFormalTimer }) diff --git a/frontend/src/views/home/components/testPopup.vue b/frontend/src/views/home/components/testPopup.vue index 7a2f0a0..9f14bcc 100644 --- a/frontend/src/views/home/components/testPopup.vue +++ b/frontend/src/views/home/components/testPopup.vue @@ -5,6 +5,9 @@ width="1550px" :model-value="dialogVisible" :before-close="beforeClose" + :close-on-click-modal="!isRestoringFormalProgress" + :close-on-press-escape="!isRestoringFormalProgress" + :show-close="!isRestoringFormalProgress" @close="handleClose" height="1000px" draggable @@ -87,6 +90,7 @@ @sendPause="sendPause" @sendResume="sendResume" @closeWebSocket="closeWebSocket" + @restoreFormalProgressRequired="handleRestoreFormalProgressRequired" :stepsActive="stepsActive" /> @@ -94,10 +98,17 @@ @@ -154,6 +176,7 @@ import { onBeforeUnmount, reactive, ref, watch } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { Coin, + DocumentChecked, Edit, Key, Odometer, @@ -170,14 +193,17 @@ import factorTest from './factorTest.vue' import test from './test.vue' import socketClient from '@/utils/webSocketClient' import { useCheckStore } from '@/stores/modules/check' -import { pauseTest, resumeTest, startPreTest } from '@/api/socket/socket' +import { useDetectionLockStore } from '@/stores/modules/detectionLock' +import { getFormalProgress, pauseTest, resumeTest, saveFormalProgress, startPreTest } from '@/api/socket/socket' import { useUserStore } from '@/stores/modules/user' import { JwtUtil } from '@/utils/jwtUtil' import mittBus, { STOP_DETECTION_TIMER_EVENT } from '@/utils/mittBus' +import type { CheckData } from '@/api/check/interface' // ====================== 状态管理 ====================== const userStore = useUserStore() const checkStore = useCheckStore() +const detectionLockStore = useDetectionLockStore() // ====================== 基础状态变量 ====================== const nextStepText = ref('下一步') // 下一步按钮文本 @@ -185,7 +211,13 @@ const dialogVisible = ref(false) // 弹窗显示状态 const dialogTitle = ref('') // 弹窗标题 const showComponent = ref(true) // 是否显示检测组件 const preTestRef = ref<{ initializeParameters: () => void } | null>(null) // 预检测组件引用 -const testRef = ref<{ handlePause: () => void } | null>(null) // 正式检测组件引用 +const testRef = ref<{ + handlePause: () => void + getElapsedSeconds: () => number + applyFormalProgress: (snapshot: CheckData.FormalProgressVO) => void + startFormalTimer: () => void +} | null>(null) // 正式检测组件引用 +const isRestoringFormalProgress = ref(false) // ====================== 步骤控制相关变量 ====================== const showSteps = ref(false) // 是否显示步骤条 @@ -356,7 +388,8 @@ const handleSubmitFast = () => { checkStore.selectTestItems.channelsTest, checkStore.selectTestItems.timeTest, checkStore.selectTestItems.test - ] + ], + resumeFormal: checkStore.resumeFormal }).then(res => { if (res.code !== 'A0000') { ElMessageBox.alert('预检测失败', '检测失败', { @@ -387,7 +420,8 @@ const handleSubmitFast = () => { checkStore.selectTestItems.channelsTest, checkStore.selectTestItems.timeTest, checkStore.selectTestItems.test - ] + ], + resumeFormal: checkStore.resumeFormal }).then(res => { if (res.code !== 'A0000') { ElMessageBox.alert('系数校准失败', '检测失败', { @@ -421,7 +455,8 @@ const handleSubmitFast = () => { checkStore.selectTestItems.channelsTest, checkStore.selectTestItems.timeTest, checkStore.selectTestItems.test - ] + ], + resumeFormal: checkStore.resumeFormal }).then(res => { if (res.code !== 'A0000') { ElMessageBox.alert('守时检测失败', '检测失败', { @@ -438,12 +473,13 @@ const handleSubmitFast = () => { case 4: // 正式检测步骤 if (TestStatus.value == 'waiting') { // 如果没有预检测、系数校准和守时检测,直接进行正式检测需要先初始化 - if ( + const needStartPreTest = !checkStore.selectTestItems.preTest && !checkStore.selectTestItems.channelsTest && !checkStore.selectTestItems.timeTest && checkStore.selectTestItems.test - ) { + TestStatus.value = 'start' + if (needStartPreTest) { startPreTest({ userPageId: JwtUtil.getLoginName(), devIds: deviceIds, @@ -457,18 +493,24 @@ const handleSubmitFast = () => { checkStore.selectTestItems.channelsTest, checkStore.selectTestItems.timeTest, checkStore.selectTestItems.test - ] + ], + resumeFormal: checkStore.resumeFormal }).then(res => { if (res.code !== 'A0000') { - ElMessageBox.alert('正式检测失败', '检测失败', { + ElMessageBox.alert(res.message || '正式检测失败', '检测失败', { confirmButtonText: '确定', type: 'error' }) - TestStatus.value = 'error' + TestStatus.value = 'test_init_fail' + return } + testRef.value?.startFormalTimer() + }).catch(() => { + TestStatus.value = 'test_init_fail' }) + } else { + testRef.value?.startFormalTimer() } - TestStatus.value = 'start' } else if (TestStatus.value == 'paused') { // 如果是暂停状态,发送继续指令 sendResume() @@ -601,13 +643,14 @@ const sendPause = () => { /** * 发送继续检测指令 */ -const sendResume = () => { +const sendResume = async () => { // 调用继续检测API - resumeTest({ + const res: any = await resumeTest({ userPageId: JwtUtil.getLoginName(), devIds: checkStore.devices.map(item => item.deviceId), planId: checkStore.plan.id, + sourceId: getCurrentSourceId(), reCheckType: '2', // 操作类型:0-系数校验,1-预检测,2-正式检测,8-不合格项复检 userId: userStore.userInfo.id, temperature: checkStore.temperature, @@ -615,11 +658,81 @@ const sendResume = () => { }) // 发送继续成功消息给子组件 + if (res.code !== 'A0000') { + ElMessage.error(res.message || '继续检测失败') + TestStatus.value = 'paused' + return + } + Object.assign(webMsgSend.value, { requestId: 'Resume_Success' }) } +const getCurrentSourceId = () => { + const plan = checkStore.plan as any + if (plan.sourceId) { + return plan.sourceId + } + if (Array.isArray(plan.sourceIds)) { + return plan.sourceIds[0] + } + if (typeof plan.sourceIds === 'string') { + return plan.sourceIds.split(',').filter(Boolean)[0] + } + return undefined +} + +const buildFormalProgressParam = () => ({ + userPageId: JwtUtil.getLoginName(), + devIds: checkStore.devices.map(item => item.deviceId), + planId: checkStore.plan.id, + userId: userStore.userInfo.id, + formalCheckTime: testRef.value?.getElapsedSeconds() ?? 0 +}) + +const sleep = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms)) + +const handleSaveFormalProgress = async () => { + try { + await ElMessageBox.confirm('是否保存当前正式检测进度并退出?', '提示', { + confirmButtonText: '是', + cancelButtonText: '否', + type: 'warning' + }) + } catch { + return + } + + const res: any = await saveFormalProgress(buildFormalProgressParam()) + if (res.code !== 'A0000') { + ElMessage.error('暂存进度失败') + return + } + ElMessage.success('暂存进度成功') + detectionLockStore.clearHolder() + handleClose() +} + +const handleRestoreFormalProgressRequired = async () => { + isRestoringFormalProgress.value = true + checkStore.setRestoringFormalProgress(true) + try { + const res: any = await getFormalProgress(buildFormalProgressParam()) + if (res.code !== 'A0000') { + ElMessage.error('恢复进度失败') + TestStatus.value = 'error' + return + } + testRef.value?.applyFormalProgress(res.data) + checkStore.setFormalProgressSnapshot(res.data) + await sleep(350) + } finally { + isRestoringFormalProgress.value = false + checkStore.setRestoringFormalProgress(false) + } +} + /** * 关闭WebSocket连接 */ @@ -713,12 +826,18 @@ function clearData() { TestStatus.value = 'waiting' ActiveStatue.value = 'waiting' nextStepText.value = '下一步' + isRestoringFormalProgress.value = false + checkStore.setRestoringFormalProgress(false) + checkStore.setFormalProgressSnapshot(null) } /** * 关闭前确认处理 */ const beforeClose = () => { + if (isRestoringFormalProgress.value) { + return + } // 如果检测未完成且不是错误状态,需要用户确认 if (stepsActiveIndex.value < stepsTotalNum.value && ActiveStatue.value != 'error') { ElMessageBox.confirm('检测未完成,是否退出当前检测流程?', '提示', { diff --git a/frontend/src/views/home/components/writeTHPopup.vue b/frontend/src/views/home/components/writeTHPopup.vue index 8cbb197..2bed63c 100644 --- a/frontend/src/views/home/components/writeTHPopup.vue +++ b/frontend/src/views/home/components/writeTHPopup.vue @@ -8,6 +8,9 @@ + + + @@ -36,17 +39,22 @@ const dialogFormRef = ref() const dialogVisible = ref(false) const formContent = reactive({ temperature: 0, humidity: 0 }) +const hasCheckingDevice = ref(false) +const resumeFormalChecked = ref(false) const timeTestChecked = ref(false) const checkStore = useCheckStore() const open = async () => { resetFormContent() - syncTimeTestSelection(false) + syncResumeFormalSelection() + timeTestChecked.value = Boolean(checkStore.selectTestItems.timeTest) dialogVisible.value = true } // 清空表单内容 const resetFormContent = () => { Object.assign(formContent, { temperature: '22', humidity: '50' }) + hasCheckingDevice.value = false + resumeFormalChecked.value = false timeTestChecked.value = false } @@ -58,6 +66,18 @@ const syncTimeTestSelection = (selected: boolean) => { }) } +const syncResumeFormalSelection = () => { + hasCheckingDevice.value = checkStore.devices.some(device => Number(device.checkState) === 1) + resumeFormalChecked.value = hasCheckingDevice.value + checkStore.setResumeFormal(hasCheckingDevice.value) +} + +const handleResumeFormalChange = (value: string | number | boolean) => { + const selected = hasCheckingDevice.value && Boolean(value) + resumeFormalChecked.value = selected + checkStore.setResumeFormal(selected) +} + const handleTimeTestChange = (value: string | number | boolean) => { syncTimeTestSelection(Boolean(value)) } @@ -89,6 +109,7 @@ const handleStart = () => { if (valid) { checkStore.setTemperature(formContent.temperature) checkStore.setHumidity(formContent.humidity) + checkStore.setResumeFormal(hasCheckingDevice.value && resumeFormalChecked.value) emit('openTestDialog2') handleClose() } diff --git a/frontend/src/views/plan/planList/components/childrenPlan.vue b/frontend/src/views/plan/planList/components/childrenPlan.vue index 82d3173..e9701ac 100644 --- a/frontend/src/views/plan/planList/components/childrenPlan.vue +++ b/frontend/src/views/plan/planList/components/childrenPlan.vue @@ -824,4 +824,4 @@ const props = withDefaults(defineProps(), { // default: 744, // }, // }>() - \ No newline at end of file + diff --git a/frontend/src/views/plan/planList/components/planStatisticsPopup.vue b/frontend/src/views/plan/planList/components/planStatisticsPopup.vue index 2c292dd..60728b8 100644 --- a/frontend/src/views/plan/planList/components/planStatisticsPopup.vue +++ b/frontend/src/views/plan/planList/components/planStatisticsPopup.vue @@ -96,6 +96,7 @@ const emptyStatistics = (): Plan.PlanStatistics => ({ totalCheckCount: 0, checkedDeviceCount: 0, uncheckedDeviceCount: 0, + checkingDeviceCount: 0, firstQualifiedDeviceCount: 0, secondQualifiedDeviceCount: 0, thirdOrMoreQualifiedDeviceCount: 0, @@ -139,6 +140,7 @@ const isEmpty = computed(() => { const summaryItems = computed(() => [ { label: '未检设备', value: statisticsData.uncheckedDeviceCount }, + { label: '检测中设备', value: statisticsData.checkingDeviceCount, type: 'is-checking' }, { label: '已检设备', value: statisticsData.checkedDeviceCount }, { label: '合格设备', value: statisticsData.qualifiedDeviceCount, type: 'is-qualified' }, { label: '不合格设备', value: statisticsData.unqualifiedDeviceCount, type: 'is-unqualified' } @@ -374,7 +376,7 @@ defineExpose({ open }) .summary-grid { display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; margin-bottom: 12px; } @@ -403,6 +405,15 @@ defineExpose({ open }) line-height: 28px; } +.summary-item.is-checking { + border-color: var(--el-color-primary-light-5); + background: var(--el-color-primary-light-9); +} + +.summary-item.is-checking .summary-value { + color: var(--el-color-primary); +} + .summary-item.is-qualified { border-color: var(--el-color-success-light-5); background: var(--el-color-success-light-9); @@ -455,7 +466,7 @@ defineExpose({ open }) @media (max-width: 1200px) { .summary-grid { - grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-columns: repeat(3, minmax(0, 1fr)); } }