8 Commits
dev ... yn

Author SHA1 Message Date
caozehui
ee45e65184 微调 2026-07-15 16:32:05 +08:00
caozehui
987b6cd57f Merge branch 'master' into yn 2026-07-08 09:06:34 +08:00
caozehui
3b6a7d9d5b 微调 2026-07-06 16:25:54 +08:00
caozehui
0379e5cf2e Merge branch 'master' into yn 2026-07-06 16:25:17 +08:00
caozehui
bc474fe17c Merge branch 'master' into yn 2026-07-06 11:03:10 +08:00
caozehui
b50ca36024 Merge branch 'master' into yn 2026-07-06 08:48:20 +08:00
caozehui
38d834170a Merge branch 'master' into yn 2026-07-02 18:36:56 +08:00
caozehui
f5f22f9635 logo 2026-07-02 17:48:21 +08:00
110 changed files with 195 additions and 1104 deletions

View File

@@ -54,13 +54,13 @@ webSocket:
port: 7778 port: 7778
#源参数下发,暂态数据默认值 #源参数下发,暂态数据默认值
#Dip: Dip:
# 暂态前时间s # 暂态前时间s
# fPreTime: 2f # fPreTime: 2f
#写入时间s #写入时间s
# fRampIn: 0.001f fRampIn: 0.001f
#写出时间s #写出时间s
# fRampOut: 0.001f fRampOut: 0.001f
# 暂态后时间s # 暂态后时间s
# fAfterTime: 3f # fAfterTime: 3f

View File

@@ -1 +1 @@
51248 55936

View File

@@ -17,4 +17,3 @@
.\binlog.000039 .\binlog.000039
.\binlog.000040 .\binlog.000040
.\binlog.000041 .\binlog.000041
.\binlog.000042

View File

@@ -1,211 +0,0 @@
# 守时检测设计
## 背景
现有守时页面 `frontend/src/views/home/components/timeTest.vue` 是演示式页面,包含假数据、步骤条和旧的“标准时间/上送时刻”展示方式,不符合新的业务流程。新的守时检测需要同时支持多台装置,动态展示每台装置的暂态触发时刻、守时误差和结果,并与现有检测弹窗、后端 `startPreTest` 流程联动。
后端项目位于 `D:\njcn\test\CN_Gather`。本设计覆盖前端与后端协同修改。
## 目标
- 守时页面只展示守时检测用时和设备结果表,不展示源连接、设备连接、协议校验、相序校验等前置阶段。
- 表格列固定为装置名称、暂态1触发时刻、暂态2触发时刻、守时误差、结果。
- 未收到对应数据时,单元格显示 loading 转圈状态。
- 守时检测计时格式为 `HH:mm:ss`
- 支持多台设备同时检测。
- 守时检测过程要像 `factorTest.vue` 一样监听并拦截 WebSocket 错误,错误时终止流程。
- 守时完成后,如果后续还有正式检测,前后端自动流向正式检测且保持组件状态;如果没有正式检测,则清理连接和 WebSocket 资源。
## 检测项契约
`PreDetectionParam.testItemList` 从 3 位改为 4 位:
1. 第 1 位:预检测
2. 第 2 位:系数校准
3. 第 3 位:守时校验
4. 第 4 位:正式检测
前端所有调用 `startPreTest` 的位置都必须发送 4 位数组,例如:
- 只做守时检测:`[false, false, true, false]`
- 预检测 + 守时检测:`[true, false, true, false]`
- 守时检测 + 正式检测:`[false, false, true, true]`
- 预检测 + 守时检测 + 正式检测:`[true, false, true, true]`
后端所有读取 `testItemList` 的逻辑同步使用新索引,不能继续把第三位解释为正式检测。
## 前端流程
### 启动规则
`testPopup.vue` 继续作为检测弹窗流程编排入口。
只勾选守时检测时,前端调用现有 `startPreTest`。接口成功返回后,认为守时检测开始,`timeTest.vue` 开始计时。后端随后执行源连接、设备连接、协议校验、相序校验,再进入守时暂态流程。
勾选预检测 + 守时检测时,守时检测不在 `startPreTest` 成功后立刻开始计时。预检测包含源连接、设备连接、协议校验、相序校验四个阶段;这四个阶段完成后,后端推送守时开始/暂态数据相关消息,前端开始守时计时并展示表格更新。
### 页面结构
`timeTest.vue` 改为轻量结果页:
- 顶部为紧凑计时区域,显示 `守时检测用时HH:mm:ss`
- 下方为结果表格,列为 `装置名称 / 暂态1触发时刻 / 暂态2触发时刻 / 守时误差 / 结果`
- 行数据初始化自 `checkStore.devices`
- 未收到数据的字段显示 Element Plus loading 转圈。
- `time_waiting_second` 消息只触发轻量提示,例如“已收到第一组暂态数据,等待第二组暂态数据”,不增加页面状态区和统计区。
### Step 切换与状态保活
守时检测完成且后续进入正式检测后,用户可以点击弹窗顶部 step 切回守时检测页。此时守时页只读展示已完成结果,不重启监听、不重置 loading、不重新计时。
用户再切回正式检测页时,正式检测组件的表格信息、计时信息、日志进度和当前检测步骤不能初始化。实现上应保持组件实例存活,只切换 `stepsActiveView` 的显示状态,避免因 step 切换触发正式检测组件重建或初始化。
### 错误处理
`timeTest.vue` 需要接收并监听 `webMsgSend`,与 `factorTest.vue` 保持一致的错误拦截思路:
- 通用错误码如 `10520``10521``10522``10523``10524``10525``10527``10528``10529``10550``10551``10552``-1`
- WebSocket 连接类错误,如 `socket_timeout``server_error``device_error``connect/Source``connect/Dev`
- 源连接、设备连接、协议校验、相序校验阶段虽然不展示在页面中,但这些阶段的错误必须被守时页捕获,弹窗提示后 `emit('update:testStatus', 'error')`,父流程终止。
## 后端流程
### 总体接入
后端采用复用现有检测链路的方案。`startPreTest` 仍作为统一入口,守时校验作为现有检测链路中的独立业务阶段插入。
单独勾选守时检测时,后端需要执行完整前置阶段:
1. 源连接
2. 设备连接
3. 协议校验
4. 相序校验
5. 守时检测暂态流程
勾选预检测 + 守时检测时,预检测已经包含前四个阶段。后端不重复执行、不关闭源连接和设备连接,直接进入守时检测暂态流程。
### 守时业务规则
守时检测使用后端内置的两段守时专用暂态脚本,不由前端传脚本编号。
业务过程:
1. 下发暂态脚本 1。
2. 收取每台装置的一组暂态数据。
3. 两次暂态触发时刻均从 `DevData.time` 读取。
4. 同一设备存在多个监测点数据时,以第一条数据为准。
5. 第一组数据收齐后,后端推送 `time_waiting_second` 事件。
6. 后端等待 `waitTime - 暂态持续时间1 - fRampOut - fPreTime - fRampIn`
7. 下发暂态脚本 2。
8. 收取每台装置第二组暂态数据。
9. 计算 `dtMs = 暂态1触发时刻 - 暂态2触发时刻 - waitTime`
10. `dtMs <= 10ms` 为合格,否则不合格。
结果判定完全由后端负责。前端只展示后端推送的守时误差和结果,不自行计算合格性。
### 守时结束后的资源处理
如果 `testItemList[3]``true`,守时检测完成后自动流向正式检测:
- 后端不关闭源连接。
- 后端不关闭设备连接。
- 后端不清理正式检测需要的流程上下文。
- 前端不关闭 WebSocket不重建正式检测组件。
- 前端收到守时成功后自动切到正式检测步骤。
如果 `testItemList[3]``false`,守时检测后没有正式检测:
- 后端在守时结束后断开设备连接和源连接。
- 后端清理检测流程上下文。
- 前端停止计时,展示最终结果,状态置为 `success``error`
- 弹窗关闭时清理 WebSocket 回调或连接。
如果守时检测失败,无论是否勾选正式检测,都终止流程,不进入正式检测,并按错误流程释放资源。
## WebSocket 消息
守时阶段使用独立的守时消息语义,避免前端混在正式检测或系数校准消息里猜状态。推荐 `requestId` 使用 `Time_Check`
### `time_first_data`
收到某台设备第一次暂态数据。
`data` 建议字段:
- `deviceId`
- `deviceName`
- `monitorId` 或监测点标识
- `firstTriggerTime`
### `time_waiting_second`
第一组暂态数据已收齐,后端已进入等待第二次暂态数据阶段。
该消息只作为事件触发标志,前端只做提示,不依赖业务字段。
### `time_second_data`
收到某台设备第二次暂态数据。
`data` 建议字段:
- `deviceId`
- `deviceName`
- `monitorId` 或监测点标识
- `secondTriggerTime`
### `time_result`
某台设备守时结果计算完成。
`data` 建议字段:
- `deviceId`
- `deviceName`
- `firstTriggerTime`
- `secondTriggerTime`
- `waitTimeMs`
- `dtMs`
- `result`
- `message`
`result` 可以使用后端现有结果约定,也可以明确为 `passed` / `failed`。前端需统一映射为“合格 / 不合格”。
### `time_finish`
全部守时检测完成。
`data` 建议字段:
- `allPassed`
- `results`
- `hasFormalTest`
前端收到后停止守时计时。如果 `hasFormalTest``true`,自动流向正式检测;否则结束当前检测流程。
## 涉及文件
前端:
- `frontend/src/views/home/components/timeTest.vue`
- `frontend/src/views/home/components/testPopup.vue`
- `frontend/src/api/socket/socket.ts`
- 必要时检查 `frontend/src/views/home/components/compareTestPopup.vue` 是否存在同类 `testItemList` 构造逻辑。
后端:
- `D:\njcn\test\CN_Gather\detection\src\main\java\com\njcn\gather\detection\pojo\param\PreDetectionParam.java`
- `D:\njcn\test\CN_Gather\detection\src\main\java\com\njcn\gather\detection\controller\PreDetectionController.java`
- `D:\njcn\test\CN_Gather\detection\src\main\java\com\njcn\gather\detection\service\impl\PreDetectionServiceImpl.java`
- `D:\njcn\test\CN_Gather\detection\src\main\java\com\njcn\gather\detection\handler\SocketDevResponseService.java`
- `D:\njcn\test\CN_Gather\detection\src\main\java\com\njcn\gather\detection\pojo\enums\SourceOperateCodeEnum.java`
## 测试建议
- 单独勾选守时检测:验证 `startPreTest` 成功后计时立即开始,前置四阶段错误可被前端拦截,最终无正式检测时资源被清理。
- 勾选预检测 + 守时检测:验证前置四阶段不重复执行、不关闭连接,守时计时从预检测四阶段完成后开始。
- 勾选守时检测 + 正式检测:验证守时成功后自动进入正式检测,连接和 WebSocket 不被清理。
- 正式检测中点击 step 切回守时页,再切回正式检测页:验证正式检测表格、计时、日志和进度不初始化。
- 多设备且多监测点:验证每台设备只取第一条监测点数据参与守时计算。
- 守时失败:验证不进入正式检测,并按错误流程释放资源。
- `time_waiting_second`:验证只提示,不改变表格结构。

View File

@@ -13,9 +13,9 @@ VITE_REPORT=false
# 浏览器是否显示vue的告警信息 # 浏览器是否显示vue的告警信息
VUE_APP_SILENCE_WARNINGS=true VUE_APP_SILENCE_WARNINGS=true
VITE_COMPANY_WEBSITE=http://www.shining-electric.com/ VITE_COMPANY_WEBSITE=https://baike.baidu.com/item/%E4%BA%91%E5%8D%97%E7%94%B5%E5%8A%9B%E6%8A%80%E6%9C%AF%E6%9C%89%E9%99%90%E8%B4%A3%E4%BB%BB%E5%85%AC%E5%8F%B8/20071982
VITE_COMPANY_NAME=南京灿能电力自动化股份有限公司 VITE_COMPANY_NAME=云南电力技术有限责任公司
VITE_IS_SHOW_RAW_DATA=true VITE_IS_SHOW_RAW_DATA=true
# 开启激活验证 # 开启激活验证
VITE_ACTIVATE_OPEN=false VITE_ACTIVATE_OPEN=true

View File

@@ -19,6 +19,6 @@ VITE_API_URL=/api
# 开发环境跨域代理,支持配置多个 # 开发环境跨域代理,支持配置多个
VITE_PROXY=[["/api","http://127.0.0.1:18092/"]] VITE_PROXY=[["/api","http://127.0.0.1:18093/"]]
#VITE_PROXY=[["/api","http://192.168.1.124:18092/"]] #VITE_PROXY=[["/api","http://192.168.1.124:18092/"]]
#VITE_PROXY=[["/api","http://192.168.2.125:18092/"]] #VITE_PROXY=[["/api","http://192.168.2.125:18092/"]]

View File

@@ -23,5 +23,5 @@ VITE_PWA=true
# 线上环境接口地址 # 线上环境接口地址
#VITE_API_URL="/api" # 打包时用 #VITE_API_URL="/api" # 打包时用
VITE_API_URL="http://127.0.0.1:18092/" VITE_API_URL="http://127.0.0.1:18093/"

View File

@@ -134,8 +134,6 @@ export namespace CheckData {
devCurr: number //设备电流 devCurr: number //设备电流
factorFlag: number //是否支持系数校准 factorFlag: number //是否支持系数校准
checkResult: number //检测结果 checkResult: number //检测结果
checkState?: number | null //检测状态
formalCheckTime?: number | null //正式检测耗时
chnNumList: string[] //连线存储数据 chnNumList: string[] //连线存储数据
} }
@@ -181,27 +179,10 @@ export namespace CheckData {
scriptType: string scriptType: string
scriptName?: string //可以不要该属性,有点多余 scriptName?: string //可以不要该属性,有点多余
code?: string code?: string
scriptCode?: string
// 设备 // 设备
devices: Array<DeviceCheckResult> devices: Array<DeviceCheckResult>
} }
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 { export enum ButtonColorEnum {
INFO = '#909399', INFO = '#909399',
LOADING = 'var(--el-color-primary)', LOADING = 'var(--el-color-primary)',

View File

@@ -1,91 +0,0 @@
import type { Device } from '@/api/device/interface/device'
import type { Monitor } from '@/api/device/interface/monitor'
const DEVICE_FIELD_NAMES = [
'id',
'name',
'pattern',
'devType',
'manufacturer',
'createDate',
'createId',
'hardwareVersion',
'softwareVersion',
'protocol',
'ip',
'port',
'encryptionFlag',
'series',
'devKey',
'sampleId',
'arrivedDate',
'cityName',
'gdName',
'subName',
'reportPath',
'icdId',
'planId',
'factorFlag',
'preinvestmentPlan',
'delegate',
'inspectChannel',
'inspectDate',
'harmSysId',
'importFlag'
] as const
const MONITOR_FIELD_NAMES = [
'id',
'devId',
'busbar',
'name',
'num',
'pt',
'ct',
'connection',
'statInterval',
'harmSysId',
'checkFlag'
] as const
function appendIfPresent(formData: FormData, key: string, value: unknown) {
if (value === null || value === undefined || value === '') {
return
}
formData.append(key, String(value))
}
function appendMonitorFields(formData: FormData, monitorList: Monitor.ResPqMon[]) {
monitorList.forEach((monitor, index) => {
MONITOR_FIELD_NAMES.forEach(fieldName => {
appendIfPresent(formData, `monitorList[${index}].${fieldName}`, monitor[fieldName])
})
})
}
export function buildPqDevFormData(device: Device.ResPqDev, imageFile?: File | null) {
const formData = new FormData()
DEVICE_FIELD_NAMES.forEach(fieldName => {
appendIfPresent(formData, fieldName, device[fieldName])
})
appendMonitorFields(formData, device.monitorList ?? [])
if (imageFile) {
formData.append('image', imageFile)
}
return formData
}
export function buildDeviceImagePreviewSrc(
hasImage?: boolean,
imageBase64?: string | null,
imageContentType?: string | null
) {
if (!hasImage || !imageBase64 || !imageContentType) {
return null
}
return `data:${imageContentType};base64,${imageBase64}`
}

View File

@@ -1,6 +1,5 @@
import type {Device} from '@/api/device/interface/device' import type {Device} from '@/api/device/interface/device'
import http from '@/api' import http from '@/api'
import { buildPqDevFormData } from './formData'
/** /**
* @name 被检设备管理模块 * @name 被检设备管理模块
@@ -12,13 +11,13 @@ export const getPqDevList = (params: Device.ReqPqDevParams) => {
} }
//添加被检设备 //添加被检设备
export const addPqDev = (params: Device.ResPqDev, imageFile?: File | null) => { export const addPqDev = (params: Device.ResPqDev) => {
return http.upload(`/pqDev/add`, buildPqDevFormData(params, imageFile)) return http.post(`/pqDev/add`, params)
} }
//编辑被检设备 //编辑被检设备
export const updatePqDev = (params: Device.ResPqDev, imageFile?: File | null) => { export const updatePqDev = (params: Device.ResPqDev) => {
return http.upload(`/pqDev/update`, buildPqDevFormData(params, imageFile)) return http.post(`/pqDev/update`, params)
} }
//删除被检设备 //删除被检设备
@@ -72,7 +71,3 @@ export const getPqDev = () => {
export const getSelectOptions = (params:{ pattern: string }) => { export const getSelectOptions = (params:{ pattern: string }) => {
return http.get(`/pqDev/getSelectOptions`, params) return http.get(`/pqDev/getSelectOptions`, params)
} }
export const downloadDeviceImage = (id: string) => {
return http.downloadGetWithHeaders(`/pqDev/image/download?id=${id}`)
}

View File

@@ -91,9 +91,6 @@ export namespace Device {
monitorList: Monitor.ResPqMon[] monitorList: Monitor.ResPqMon[]
checked?: boolean // 是否已选择 checked?: boolean // 是否已选择
disabled?: boolean // 是否禁用 disabled?: boolean // 是否禁用
hasImage?: boolean // 是否存在设备图片
imageBase64?: string | null // 设备图片 Base64用于详情回显
imageContentType?: string | null // 设备图片 MIME 类型
} }
export interface SelectOption { export interface SelectOption {

View File

@@ -86,7 +86,6 @@ export namespace Plan {
totalCheckCount: number; totalCheckCount: number;
checkedDeviceCount: number; checkedDeviceCount: number;
uncheckedDeviceCount: number; uncheckedDeviceCount: number;
checkingDeviceCount: number;
firstQualifiedDeviceCount: number; firstQualifiedDeviceCount: number;
secondQualifiedDeviceCount: number; secondQualifiedDeviceCount: number;
thirdOrMoreQualifiedDeviceCount: number; thirdOrMoreQualifiedDeviceCount: number;

View File

@@ -1,6 +1,5 @@
import http from '@/api' import http from '@/api'
import { useDetectionLockStore } from '@/stores/modules/detectionLock' import { useDetectionLockStore } from '@/stores/modules/detectionLock'
import type { CheckData } from '@/api/check/interface'
export const startPreTest = async (params) => { export const startPreTest = async (params) => {
@@ -38,14 +37,6 @@ export const resumeTest = (params) => {
return http.post(`/prepare/restartTemTest/`, params, {loading: false}) 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<CheckData.FormalProgressVO>(`/prepare/formalProgress`, params, { loading: false })
}
/** /**
* 比对式通道配对 * 比对式通道配对
* @param params * @param params

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -10,7 +10,7 @@
padding: 0 15px 0 0; padding: 0 15px 0 0;
border-bottom: 1px solid var(--el-header-border-color); border-bottom: 1px solid var(--el-header-border-color);
// background-color: var(--el-header-bg-color); // background-color: var(--el-header-bg-color);
background-color: var(--el-color-primary); //默认蓝色风格背景 background-color: #fff; //默认蓝色风格背景
.logo { .logo {
padding:0 10px 0 15px; padding:0 10px 0 15px;
margin-right: 30px; margin-right: 30px;
@@ -23,7 +23,7 @@
font-size: 21.5px; font-size: 21.5px;
font-weight: bold; font-weight: bold;
// color: var(--el-header-logo-text-color); // color: var(--el-header-logo-text-color);
color: #fff; // logo文字颜色 color: var(--el-color-primary); // logo文字颜色
white-space: nowrap; white-space: nowrap;
} }
} }
@@ -33,15 +33,14 @@
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
border-bottom: none; border-bottom: none;
background-color: var(--el-color-primary); //导航蓝色背景 background-color: #fff; //导航蓝色背景
.el-menu-item { .el-menu-item {
color: #fff; color: var(--el-color-primary);
} }
.el-menu-item:hover { .el-menu-item:hover {
color: #fff; //一级导航划过颜色 color: #fff; //一级导航划过颜色
//background-color: #5274a5 !important; //一级导航划过背景色 //background-color: #5274a5 !important; //一级导航划过背景色
background-color:var(--el-color-primary-light-3) !important; background-color:var(--el-color-primary-light-4) !important;
} }
.el-sub-menu__hide-arrow { .el-sub-menu__hide-arrow {
width: 65px; width: 65px;
@@ -50,15 +49,19 @@
.el-menu-item.is-active { .el-menu-item.is-active {
color: #fff !important; //一级导航文字选中颜色 color: #fff !important; //一级导航文字选中颜色
//background-color: #5274a5 !important; //一级导航选中背景色 //background-color: #5274a5 !important; //一级导航选中背景色
background-color: var(--el-color-primary-light-3) !important; background-color: var(--el-color-primary-light-4) !important;
border-bottom: 0 !important; border-bottom: 0 !important;
} }
.el-sub-menu__title:hover {
color: #fff !important;
background-color: var(--el-color-primary-light-4) !important;
}
.el-sub-menu__title { .el-sub-menu__title {
color: #fff; color: var(--el-color-primary);
} }
.is-opened:hover { .is-opened:hover {
color: #fff; color: var(--el-color-primary);
} }
.is-active { .is-active {
background-color: var(--el-color-primary) !important; background-color: var(--el-color-primary) !important;
@@ -68,12 +71,12 @@
width: 0; width: 0;
} }
.el-sub-menu__title { .el-sub-menu__title {
color: #fff !important; //二级导航文字选中颜色 color: var(--el-color-primary) !important; //二级导航文字选中颜色
// background-color: var(--el-color-primary) !important; // background-color: var(--el-color-primary) !important;
// background-color: #5274a5 !important;//二级导航选中背景色 // background-color: #5274a5 !important;//二级导航选中背景色
//background-color: #7588e5 !important; //background-color: #7588e5 !important;
background-color: var(--el-color-primary-light-3) !important; background-color: var(--el-color-primary-light-4) !important;
border-bottom-color: var(--el-color-primary) !important; border-bottom-color: var(--el-color-primary) !important;
} }
} }

View File

@@ -20,10 +20,7 @@ export const useCheckStore = defineStore(CHECK_STORE_KEY, {
temperature: 0, temperature: 0,
humidity: 0, humidity: 0,
chnNumList: [] as string[], //连线数据 chnNumList: [] as string[], //连线数据
nodesConnectable: true, //设置是能可以连线 nodesConnectable: true //设置是能可以连线
resumeFormal: false,
restoringFormalProgress: false,
formalProgressSnapshot: null as CheckData.FormalProgressVO | null
}), }),
getters: {}, getters: {},
actions: { actions: {
@@ -39,7 +36,6 @@ export const useCheckStore = defineStore(CHECK_STORE_KEY, {
initSelectTestItems() { initSelectTestItems() {
const appSceneStore = useAppSceneStore() const appSceneStore = useAppSceneStore()
this.selectTestItems.preTest = true this.selectTestItems.preTest = true
this.selectTestItems.timeTest = false
if (appSceneStore.currentScene === '1') { if (appSceneStore.currentScene === '1') {
this.selectTestItems.channelsTest = true this.selectTestItems.channelsTest = true
} else { } else {
@@ -70,15 +66,6 @@ export const useCheckStore = defineStore(CHECK_STORE_KEY, {
}, },
setNodesConnectable(nodesConnectable: boolean) { setNodesConnectable(nodesConnectable: boolean) {
this.nodesConnectable = nodesConnectable this.nodesConnectable = nodesConnectable
},
setResumeFormal(resumeFormal: boolean) {
this.resumeFormal = resumeFormal
},
setRestoringFormalProgress(restoring: boolean) {
this.restoringFormalProgress = restoring
},
setFormalProgressSnapshot(snapshot: CheckData.FormalProgressVO | null) {
this.formalProgressSnapshot = snapshot
} }
} }
}) })

View File

@@ -355,7 +355,7 @@ const handleSubmitAgain = async () => {
devIds: devIds.value, devIds: devIds.value,
standardDevIds: standardDevIds.value, standardDevIds: standardDevIds.value,
pairs: pairs.value, pairs: pairs.value,
testItemList: [checkStore.selectTestItems.preTest, false, false, checkStore.selectTestItems.test], testItemList: [checkStore.selectTestItems.preTest, false, checkStore.selectTestItems.test],
userId: userStore.userInfo.id, userId: userStore.userInfo.id,
phaseCheck: xiangXuIsShow.value ? 1 : 0, phaseCheck: xiangXuIsShow.value ? 1 : 0,
}) })
@@ -423,7 +423,7 @@ const handleSubmitFast = async () => {
devIds: devIds.value, devIds: devIds.value,
standardDevIds: standardDevIds.value, standardDevIds: standardDevIds.value,
pairs: pairs.value, pairs: pairs.value,
testItemList: [checkStore.selectTestItems.preTest, false, false, checkStore.selectTestItems.test], testItemList: [checkStore.selectTestItems.preTest, false, checkStore.selectTestItems.test],
userId: userStore.userInfo.id, userId: userStore.userInfo.id,
phaseCheck: xiangXuIsShow.value ? 1 : 0, phaseCheck: xiangXuIsShow.value ? 1 : 0,
@@ -448,7 +448,7 @@ const handleSubmitFast = async () => {
devIds: devIds.value, devIds: devIds.value,
standardDevIds: standardDevIds.value, standardDevIds: standardDevIds.value,
pairs: pairs.value, pairs: pairs.value,
testItemList: [checkStore.selectTestItems.preTest, false, false, checkStore.selectTestItems.test], testItemList: [checkStore.selectTestItems.preTest, false, checkStore.selectTestItems.test],
userId: userStore.userInfo.id, userId: userStore.userInfo.id,
phaseCheck: xiangXuIsShow.value ? 1 : 0, phaseCheck: xiangXuIsShow.value ? 1 : 0,
@@ -571,8 +571,7 @@ const sendReCheck = () => {
humidity: checkStore.humidity, humidity: checkStore.humidity,
testItemList: [ testItemList: [
checkStore.selectTestItems.preTest, checkStore.selectTestItems.preTest,
false, checkStore.selectTestItems.channelsTest,
false,
checkStore.selectTestItems.test checkStore.selectTestItems.test
] ]
}).then(res => { }).then(res => {

View File

@@ -396,7 +396,7 @@ const updateTableData = async () => {
for (let key of keys1) { for (let key of keys1) {
let children = [] let children = []
let label = key let label = key
if ((key.includes('谐波') || key.includes('简谐波')) && key !== '谐波有功功率') { if ((key.includes('谐波') || key.includes('简谐波')) || key.includes('谐波有功功率')) {
for (let item of data.resultData[key]) { for (let item of data.resultData[key]) {
let num = formatHarmNum(item.harmNum + '') let num = formatHarmNum(item.harmNum + '')
label = label =
@@ -433,7 +433,7 @@ const updateTableData = async () => {
let keys2 = Object.keys(data.rawData) let keys2 = Object.keys(data.rawData)
let rawData = new Map() let rawData = new Map()
for (let key of keys2) { for (let key of keys2) {
if ((key.includes('谐波') || key.includes('简谐波')) && key !== '谐波有功功率') { if ((key.includes('谐波') || key.includes('简谐波')) || key.includes('谐波有功功率')) {
for (let item of data.rawData[key]) { for (let item of data.rawData[key]) {
let num = formatHarmNum(item.harmNum + '') let num = formatHarmNum(item.harmNum + '')
let value = rawData.get(key + '_' + num) let value = rawData.get(key + '_' + num)

View File

@@ -1,30 +1,18 @@
<template> <template>
<el-dialog title="选择测试内容" v-model='dialogVisible' @close="handleClose" v-bind="dialogSmall" > <el-dialog title="选择测试内容" v-model='dialogVisible' @close="handleClose" v-bind="dialogSmall" >
<div> <div>
<el-form ref="dialogFormRef" :model="formContent" class="form-three select-test-form"> <el-form ref="dialogFormRef" :model="formContent" class="form-three">
<el-form-item prop="preTest"> <el-form-item prop="preTest" :label-width="100">
<div class="test-item-checkbox"> <el-checkbox v-model="formContent.preTest" label="预检测"/>
<span>预检测</span>
<el-checkbox v-model="formContent.preTest"/>
</div>
</el-form-item> </el-form-item>
<el-form-item v-if="checkStore.plan.timeCheck===1" prop="timeTest"> <el-form-item v-if="checkStore.plan.timeCheck===1" prop="timeTest" :label-width="100">
<div class="test-item-checkbox"> <el-checkbox v-model="formContent.timeTest" label="守时检测"/>
<span>守时检测</span>
<el-checkbox v-model="formContent.timeTest"/>
</div>
</el-form-item> </el-form-item>
<el-form-item v-if="channelsTestShow" prop="channelsTest"> <el-form-item v-if="channelsTestShow" prop="channelsTest" :label-width="100">
<div class="test-item-checkbox"> <el-checkbox v-model="formContent.channelsTest" label="系数校准"/>
<span>系数校准</span>
<el-checkbox v-model="formContent.channelsTest"/>
</div>
</el-form-item> </el-form-item>
<el-form-item prop="test"> <el-form-item prop="test" :label-width="100">
<div class="test-item-checkbox"> <el-checkbox v-model="formContent.test" label="正式检测"/>
<span>正式检测</span>
<el-checkbox v-model="formContent.test"/>
</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@@ -135,30 +123,5 @@ defineExpose({open})
<style scoped lang="scss"> <style scoped lang="scss">
.test-item-checkbox {
display: inline-flex;
align-items: center;
justify-content: space-between;
min-width: 92px;
white-space: nowrap;
}
.select-test-form {
display: grid;
grid-template-columns: repeat(2, 120px);
justify-content: center;
column-gap: 70px;
row-gap: 22px;
padding: 12px 0 18px;
:deep(.el-form-item) {
width: auto !important;
margin: 0 !important;
}
:deep(.el-form-item__content) {
flex: none;
justify-content: flex-start;
}
}
</style> </style>

View File

@@ -483,11 +483,7 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
sortable: true, sortable: true,
isShow: checkStateShow, isShow: checkStateShow,
render: scope => { render: scope => {
return scope.row.checkState === 0 ? '未检' return scope.row.checkState === 0 ? '未检' : scope.row.checkState === 1 ? '检测中' : scope.row.checkState === 2 ? '检测完成':'归档'
: scope.row.checkState === 1 ? '检测中'
: scope.row.checkState === 2 ? '检测完成'
: scope.row.checkState === 3 ? '归档'
: ''
} }
}, },
{ {
@@ -590,9 +586,7 @@ const handleSelectionChange = (selection: any[]) => {
devVolt: item.devVolt, devVolt: item.devVolt,
devCurr: item.devCurr, devCurr: item.devCurr,
factorFlag: item.factorFlag, factorFlag: item.factorFlag,
checkResult: item.checkResult, checkResult: item.checkResult
checkState: item.checkState,
formalCheckTime: item.formalCheckTime
} }
}) })
@@ -606,16 +600,14 @@ const handleSelectionChange = (selection: any[]) => {
const isUncheckedDevice = (device: Device.ResPqDev) => Number(device.checkState) === 0 || Number(device.checkResult) === 2 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 hasUncheckedSelectedDevice = () => channelsSelection.value.some(device => isUncheckedDevice(device))
const hasCheckedUnqualifiedSelectedDevice = () => const hasCheckedUnqualifiedSelectedDevice = () =>
channelsSelection.value.some(device => !isUncheckedDevice(device) && Number(device.checkResult) === 0) channelsSelection.value.some(device => !isUncheckedDevice(device) && Number(device.checkResult) === 0)
const isCheckedUnqualifiedDevice = (device: Device.ResPqDev) => const shouldShowRecheckModeDialog = () => hasCheckedSelectedDevice()
Number(device.checkState) === 2 && Number(device.checkResult) === 0
const shouldShowRecheckModeDialog = () =>
channelsSelection.value.length > 0 && channelsSelection.value.every(isCheckedUnqualifiedDevice)
const canUseUnqualifiedItemRecheck = () => hasCheckedUnqualifiedSelectedDevice() && !hasUncheckedSelectedDevice() const canUseUnqualifiedItemRecheck = () => hasCheckedUnqualifiedSelectedDevice() && !hasUncheckedSelectedDevice()
@@ -661,7 +653,7 @@ function tableHeaderInit(val: number) {
switch (val) { switch (val) {
case 0: // 设备检测模式 case 0: // 设备检测模式
case 1: // 手动检测模式 case 1: // 手动检测模式
checkStateTable.value = [0, 1, 2] // 显示未检、检测中、检测完成状态 checkStateTable.value = [0, 1, 2] // 显示所有检测状态
tableKey.value++ tableKey.value++
operationShow.value = false // 隐藏操作列 operationShow.value = false // 隐藏操作列
documentStateShow.value = false // 隐藏文档状态 documentStateShow.value = false // 隐藏文档状态
@@ -673,7 +665,7 @@ function tableHeaderInit(val: number) {
break break
case 3: // 报告生成模式 case 3: // 报告生成模式
if (modeStore.currentMode === '比对式') { if (modeStore.currentMode === '比对式') {
checkStateTable.value = [2, 3] // 显示检测完成和归档状态 checkStateTable.value = [2, 3] // 显示检测中,检测完成和归档状态
} else { } else {
checkStateTable.value = [2, 3] // 显示检测完成和归档状态 checkStateTable.value = [2, 3] // 显示检测完成和归档状态
} }
@@ -697,7 +689,7 @@ function tableHeaderInit(val: number) {
break break
case 5: // 数据查询模式 case 5: // 数据查询模式
if (modeStore.currentMode === '比对式') { if (modeStore.currentMode === '比对式') {
checkStateTable.value = [1, 2, 3] // 显示检测中检测完成和归档状态 checkStateTable.value = [1, 2, 3] // 显示检测中检测完成和归档状态
} else { } else {
checkStateTable.value = [2, 3] // 显示检测完成和归档状态 checkStateTable.value = [2, 3] // 显示检测完成和归档状态
} }
@@ -922,14 +914,6 @@ const handleTest = async (val: string) => {
// 处理检测相关操作 // 处理检测相关操作
if (val === '手动检测' || val === '一键检测' || val === '系数校准') { 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 checkStates = channelsSelection.value.map(item => item.checkState)
const allCheckStatesEqual = new Set(checkStates).size <= 1 const allCheckStatesEqual = new Set(checkStates).size <= 1

View File

@@ -105,7 +105,7 @@ import { InfoFilled, Loading } from '@element-plus/icons-vue'
// 单通道单测试项详情弹窗组件 // 单通道单测试项详情弹窗组件
import dataCheckSingleChannelSingleTestPopup from './dataCheckSingleChannelSingleTestPopup.vue' import dataCheckSingleChannelSingleTestPopup from './dataCheckSingleChannelSingleTestPopup.vue'
// Vue 3 Composition API // Vue 3 Composition API
import { computed, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, toRef, watch } from 'vue' import { computed, onBeforeUnmount, onMounted, reactive, ref, toRef, watch } from 'vue'
// 对话框大小绑定工具 // 对话框大小绑定工具
import { dialogBig } from '@/utils/elementBind' import { dialogBig } from '@/utils/elementBind'
// 检测数据类型定义 // 检测数据类型定义
@@ -151,14 +151,7 @@ const props = defineProps({
}) })
// 定义向父组件发送的事件 // 定义向父组件发送的事件
const emit = defineEmits([ const emit = defineEmits(['update:testStatus', 'update:webMsgSend', 'sendPause', 'sendResume', 'closeWebSocket'])
'update:testStatus',
'update:webMsgSend',
'sendPause',
'sendResume',
'closeWebSocket',
'restoreFormalProgressRequired'
])
// ========== 界面状态相关 ========== // ========== 界面状态相关 ==========
// 测试项进度抽屉是否打开 // 测试项进度抽屉是否打开
@@ -291,6 +284,13 @@ watch(testStatus, function(newValue, oldValue) {
// 直接进入检测流程 // 直接进入检测流程
emit('update:testStatus', 'process') emit('update:testStatus', 'process')
} }
// 开始计时
startTimeCount()
// 显示测试日志抽屉
showTestLog()
// 重置时间记录
startData.value = new Date()
timeDifference.value = 0
} }
// 需要停止计时的状态 // 需要停止计时的状态
@@ -552,8 +552,14 @@ watch(webMsgSend, function(newValue, oldValue) {
switch (newValue.operateCode) { switch (newValue.operateCode) {
case 'VERIFY_MAPPING$01': case 'VERIFY_MAPPING$01':
if (newValue.code == 25001) { if (newValue.code == 25001) {
ElMessage.success('初始化成功!')
testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:协议校验成功!` }) 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 percentage.value = 3
// 初始化完成,准备开始正式检测
activeIndex = getNextActiveIndex() + 2
emit('update:testStatus', 'process')
} else if (newValue.code == 25002) { } else if (newValue.code == 25002) {
let data = JSON.parse(newValue.data) let data = JSON.parse(newValue.data)
ElMessageBox.alert(`脚本与icd校验失败icd名称${data['icdType']} -> 校验项:${data['dataType']}`, '初始化失败', { ElMessageBox.alert(`脚本与icd校验失败icd名称${data['icdType']} -> 校验项:${data['dataType']}`, '初始化失败', {
@@ -579,62 +585,6 @@ watch(webMsgSend, function(newValue, oldValue) {
break break
} }
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': case 'preStopTest':
if (newValue.operateCode == 'stop') { if (newValue.operateCode == 'stop') {
@@ -748,9 +698,6 @@ watch(webMsgSend, function(newValue, oldValue) {
// ========== 检测项处理函数 ========== // ========== 检测项处理函数 ==========
// 处理检测项开始消息 // 处理检测项开始消息
const handleStartItem = (code: string, desc: string | undefined) => { const handleStartItem = (code: string, desc: string | undefined) => {
if (!timer) {
startTimeCount()
}
if (desc === undefined) { if (desc === undefined) {
// 大测试项开始 // 大测试项开始
activeIndex = getActiveIndex(code) activeIndex = getActiveIndex(code)
@@ -1015,7 +962,7 @@ const updateLog = (isStart: boolean) => {
} }
// 更新总时间显示 // 更新总时间显示
// timeView is driven by timeCount so resumed formal tests keep base + current elapsed time. timeView.value = secondToTime(timeDifference.value / 1000)
// 如果是最后一个测试项,记录总结日志 // 如果是最后一个测试项,记录总结日志
if (activeIndex === checkTotal) { if (activeIndex === checkTotal) {
testLogList.push({ testLogList.push({
@@ -1261,13 +1208,6 @@ const startTimeCount = () => {
} }
} }
const startFormalTimer = () => {
startTimeCount()
showTestLog()
startData.value = new Date()
timeDifference.value = 0
}
// 停止计时 // 停止计时
const stopTimeCount = () => { const stopTimeCount = () => {
if (timer) { if (timer) {
@@ -1303,36 +1243,6 @@ const secondToTime = (second: number) => {
return h + ':' + m + ':' + s 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(() => { onMounted(() => {
mittBus.on(STOP_DETECTION_TIMER_EVENT, handleStopDetectionTimer) mittBus.on(STOP_DETECTION_TIMER_EVENT, handleStopDetectionTimer)
}) })
@@ -1363,9 +1273,6 @@ onBeforeUnmount(() => {
// 暴露给父组件的方法 // 暴露给父组件的方法
defineExpose({ defineExpose({
handlePause, // 暂停方法 handlePause, // 暂停方法
getElapsedSeconds,
applyFormalProgress,
startFormalTimer
}) })
</script> </script>

View File

@@ -5,9 +5,6 @@
width="1550px" width="1550px"
:model-value="dialogVisible" :model-value="dialogVisible"
:before-close="beforeClose" :before-close="beforeClose"
:close-on-click-modal="!isRestoringFormalProgress"
:close-on-press-escape="!isRestoringFormalProgress"
:show-close="!isRestoringFormalProgress"
@close="handleClose" @close="handleClose"
height="1000px" height="1000px"
draggable draggable
@@ -31,18 +28,18 @@
:icon="stepsActive > 1 || stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : Edit" :icon="stepsActive > 1 || stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : Edit"
@click="handleStepClick(1)" @click="handleStepClick(1)"
/> />
<!-- 系数校准步骤 -->
<el-step
v-if="channelsTestSelected"
title="系数校准"
:icon="stepsActive > 2 || stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : Odometer"
@click="handleStepClick(2)"
/>
<!-- 守时检测步骤 --> <!-- 守时检测步骤 -->
<el-step <el-step
v-if="timeTestSelected" v-if="timeTestSelected"
title="守时检测" title="守时检测"
:icon="stepsActive > 3 || stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : UploadFilled" :icon="stepsActive > 2 || stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : UploadFilled"
@click="handleStepClick(2)"
/>
<!-- 系数校准步骤 -->
<el-step
v-if="channelsTestSelected"
title="系数校准"
:icon="stepsActive > 3 || stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : Odometer"
@click="handleStepClick(3)" @click="handleStepClick(3)"
/> />
<!-- 正式检测步骤 --> <!-- 正式检测步骤 -->
@@ -69,13 +66,13 @@
<!-- 守时检测组件 --> <!-- 守时检测组件 -->
<timeTest <timeTest
v-if="showComponent && timeTestSelected" v-if="showComponent && timeTestSelected"
v-show="timeTestSelected && stepsActiveView == 3" v-show="timeTestSelected && stepsActiveView == 2"
v-model:testStatus="timeTestStatus" v-model:testStatus="timeTestStatus"
/> />
<!-- 系数校准检测组件 --> <!-- 系数校准检测组件 -->
<factorTest <factorTest
v-if="showComponent && channelsTestSelected" v-if="showComponent && channelsTestSelected"
v-show="channelsTestSelected && stepsActiveView == 2" v-show="channelsTestSelected && stepsActiveView == 3"
v-model:testStatus="channelsTestStatus" v-model:testStatus="channelsTestStatus"
:webMsgSend="webMsgSend" :webMsgSend="webMsgSend"
/> />
@@ -90,7 +87,6 @@
@sendPause="sendPause" @sendPause="sendPause"
@sendResume="sendResume" @sendResume="sendResume"
@closeWebSocket="closeWebSocket" @closeWebSocket="closeWebSocket"
@restoreFormalProgressRequired="handleRestoreFormalProgressRequired"
:stepsActive="stepsActive" :stepsActive="stepsActive"
/> />
@@ -98,17 +94,10 @@
<template #footer> <template #footer>
<div> <div>
<!-- 开始检测按钮 - 当前状态为等待时显示 --> <!-- 开始检测按钮 - 当前状态为等待时显示 -->
<el-button type="primary" :icon="VideoPlay" v-if="ActiveStatue === 'waiting'" :disabled="isRestoringFormalProgress" @click="handleSubmitFast"> <el-button type="primary" :icon="VideoPlay" v-if="ActiveStatue === 'waiting'" @click="handleSubmitFast">
开始检测 开始检测
</el-button> </el-button>
<el-button type="primary" v-if="isRestoringFormalProgress" disabled>
<el-icon class="loading-box" style="color: #fff; margin-right: 8px">
<component :is="Refresh" />
</el-icon>
恢复进度中
</el-button>
<!-- 初始化中按钮 - 禁用状态显示加载动画 --> <!-- 初始化中按钮 - 禁用状态显示加载动画 -->
<el-button type="primary" v-if="TestStatus === 'test_init'" disabled> <el-button type="primary" v-if="TestStatus === 'test_init'" disabled>
<el-icon class="loading-box" style="color: #fff; margin-right: 8px"> <el-icon class="loading-box" style="color: #fff; margin-right: 8px">
@@ -118,7 +107,7 @@
</el-button> </el-button>
<!-- 停止检测按钮 - 检测进行中时显示 --> <!-- 停止检测按钮 - 检测进行中时显示 -->
<el-button type="primary" v-if="TestStatus == 'process'" :icon="VideoPause" :disabled="isRestoringFormalProgress" @click="handlePause()"> <el-button type="primary" v-if="TestStatus == 'process'" :icon="VideoPause" @click="handlePause()">
停止检测 停止检测
</el-button> </el-button>
@@ -131,20 +120,10 @@
</el-button> </el-button>
<!-- 继续检测按钮 - 检测已暂停时显示 --> <!-- 继续检测按钮 - 检测已暂停时显示 -->
<el-button type="warning" v-if="TestStatus == 'paused'" :icon="VideoPlay" :disabled="isRestoringFormalProgress" @click="sendResume"> <el-button type="warning" v-if="TestStatus == 'paused'" :icon="VideoPlay" @click="sendResume">
继续检测 继续检测
</el-button> </el-button>
<el-button
type="primary"
v-if="TestStatus == 'paused'"
:icon="DocumentChecked"
:disabled="isRestoringFormalProgress"
@click="handleSaveFormalProgress"
>
暂存并退出
</el-button>
<!-- 下一步/完成/错误状态按钮 --> <!-- 下一步/完成/错误状态按钮 -->
<el-button <el-button
:type="ActiveStatue === 'success' ? 'primary' : 'danger'" :type="ActiveStatue === 'success' ? 'primary' : 'danger'"
@@ -157,14 +136,13 @@
ActiveStatue === 'pause_timeout'|| ActiveStatue === 'pause_timeout'||
ActiveStatue === 'test_init_fail') ActiveStatue === 'test_init_fail')
" "
:disabled="isRestoringFormalProgress"
@click="nextStep" @click="nextStep"
> >
{{ nextStepText }} {{ nextStepText }}
</el-button> </el-button>
<!-- 退出检测按钮 - 默认显示 --> <!-- 退出检测按钮 - 默认显示 -->
<el-button type="primary" :disabled="isRestoringFormalProgress" @click="handleQuit" v-else>退出检测</el-button> <el-button type="primary" @click="handleQuit" v-else>退出检测</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -176,7 +154,6 @@ import { onBeforeUnmount, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { import {
Coin, Coin,
DocumentChecked,
Edit, Edit,
Key, Key,
Odometer, Odometer,
@@ -193,17 +170,14 @@ import factorTest from './factorTest.vue'
import test from './test.vue' import test from './test.vue'
import socketClient from '@/utils/webSocketClient' import socketClient from '@/utils/webSocketClient'
import { useCheckStore } from '@/stores/modules/check' import { useCheckStore } from '@/stores/modules/check'
import { useDetectionLockStore } from '@/stores/modules/detectionLock' import { pauseTest, resumeTest, startPreTest } from '@/api/socket/socket'
import { getFormalProgress, pauseTest, resumeTest, saveFormalProgress, startPreTest } from '@/api/socket/socket'
import { useUserStore } from '@/stores/modules/user' import { useUserStore } from '@/stores/modules/user'
import { JwtUtil } from '@/utils/jwtUtil' import { JwtUtil } from '@/utils/jwtUtil'
import mittBus, { STOP_DETECTION_TIMER_EVENT } from '@/utils/mittBus' import mittBus, { STOP_DETECTION_TIMER_EVENT } from '@/utils/mittBus'
import type { CheckData } from '@/api/check/interface'
// ====================== 状态管理 ====================== // ====================== 状态管理 ======================
const userStore = useUserStore() const userStore = useUserStore()
const checkStore = useCheckStore() const checkStore = useCheckStore()
const detectionLockStore = useDetectionLockStore()
// ====================== 基础状态变量 ====================== // ====================== 基础状态变量 ======================
const nextStepText = ref('下一步') // 下一步按钮文本 const nextStepText = ref('下一步') // 下一步按钮文本
@@ -211,13 +185,7 @@ const dialogVisible = ref(false) // 弹窗显示状态
const dialogTitle = ref('') // 弹窗标题 const dialogTitle = ref('') // 弹窗标题
const showComponent = ref(true) // 是否显示检测组件 const showComponent = ref(true) // 是否显示检测组件
const preTestRef = ref<{ initializeParameters: () => void } | null>(null) // 预检测组件引用 const preTestRef = ref<{ initializeParameters: () => void } | null>(null) // 预检测组件引用
const testRef = ref<{ const testRef = ref<{ handlePause: () => void } | null>(null) // 正式检测组件引用
handlePause: () => void
getElapsedSeconds: () => number
applyFormalProgress: (snapshot: CheckData.FormalProgressVO) => void
startFormalTimer: () => void
} | null>(null) // 正式检测组件引用
const isRestoringFormalProgress = ref(false)
// ====================== 步骤控制相关变量 ====================== // ====================== 步骤控制相关变量 ======================
const showSteps = ref(false) // 是否显示步骤条 const showSteps = ref(false) // 是否显示步骤条
@@ -287,20 +255,20 @@ const initOperate = () => {
} }
stepsTotalNum.value = count + 1 stepsTotalNum.value = count + 1
// 根据选中的检测项,设置初始显示的步骤(按优先级:预检测 > 系数校准 > 守时检测 > 正式检测) // 根据选中的检测项,设置初始显示的步骤(按优先级:预检测 > 守时检测 > 系数校准 > 正式检测)
if (preTestSelected.value) { if (preTestSelected.value) {
stepsActiveView.value = 1 // 显示预检测组件 stepsActiveView.value = 1 // 显示预检测组件
stepsActive.value = 1 // 业务逻辑设为预检测 stepsActive.value = 1 // 业务逻辑设为预检测
return return
} }
if (channelsTestSelected.value) { if (timeTestSelected.value) {
stepsActiveView.value = 2 // 显示系数校准组件 stepsActiveView.value = 2 // 显示守时检测组件
stepsActive.value = 2 // 业务逻辑设为系数校准 stepsActive.value = 2 // 业务逻辑设为守时检测
return return
} }
if (timeTestSelected.value) { if (channelsTestSelected.value) {
stepsActiveView.value = 3 // 显示守时检测组件 stepsActiveView.value = 3 // 显示系数校准组件
stepsActive.value = 3 // 业务逻辑设为守时检测 stepsActive.value = 3 // 业务逻辑设为系数校准
return return
} }
if (testSelected.value) { if (testSelected.value) {
@@ -386,10 +354,8 @@ const handleSubmitFast = () => {
testItemList: [ testItemList: [
checkStore.selectTestItems.preTest, checkStore.selectTestItems.preTest,
checkStore.selectTestItems.channelsTest, checkStore.selectTestItems.channelsTest,
checkStore.selectTestItems.timeTest,
checkStore.selectTestItems.test checkStore.selectTestItems.test
], ]
resumeFormal: checkStore.resumeFormal
}).then(res => { }).then(res => {
if (res.code !== 'A0000') { if (res.code !== 'A0000') {
ElMessageBox.alert('预检测失败', '检测失败', { ElMessageBox.alert('预检测失败', '检测失败', {
@@ -403,7 +369,10 @@ const handleSubmitFast = () => {
} }
} }
break break
case 2: // 系数校准步骤 case 2: // 守时检测步骤
timeTestStatus.value = 'start'
break
case 3: // 系数校准步骤
if (channelsTestStatus.value == 'waiting') { if (channelsTestStatus.value == 'waiting') {
// 如果没有预检测且有系数校准,需要先进行初始化 // 如果没有预检测且有系数校准,需要先进行初始化
if (!checkStore.selectTestItems.preTest && checkStore.selectTestItems.channelsTest) { if (!checkStore.selectTestItems.preTest && checkStore.selectTestItems.channelsTest) {
@@ -418,10 +387,8 @@ const handleSubmitFast = () => {
testItemList: [ testItemList: [
checkStore.selectTestItems.preTest, checkStore.selectTestItems.preTest,
checkStore.selectTestItems.channelsTest, checkStore.selectTestItems.channelsTest,
checkStore.selectTestItems.timeTest,
checkStore.selectTestItems.test checkStore.selectTestItems.test
], ]
resumeFormal: checkStore.resumeFormal
}).then(res => { }).then(res => {
if (res.code !== 'A0000') { if (res.code !== 'A0000') {
ElMessageBox.alert('系数校准失败', '检测失败', { ElMessageBox.alert('系数校准失败', '检测失败', {
@@ -435,12 +402,13 @@ const handleSubmitFast = () => {
channelsTestStatus.value = 'start' channelsTestStatus.value = 'start'
} }
break break
case 3: // 守时检测步骤 case 4: // 正式检测步骤
if (timeTestStatus.value == 'waiting') { if (TestStatus.value == 'waiting') {
// 如果没有预检测和系数校准,直接进行正式检测需要先初始化
if ( if (
!checkStore.selectTestItems.preTest && !checkStore.selectTestItems.preTest &&
!checkStore.selectTestItems.channelsTest && !checkStore.selectTestItems.channelsTest &&
checkStore.selectTestItems.timeTest checkStore.selectTestItems.test
) { ) {
startPreTest({ startPreTest({
userPageId: JwtUtil.getLoginName(), userPageId: JwtUtil.getLoginName(),
@@ -453,64 +421,19 @@ const handleSubmitFast = () => {
testItemList: [ testItemList: [
checkStore.selectTestItems.preTest, checkStore.selectTestItems.preTest,
checkStore.selectTestItems.channelsTest, checkStore.selectTestItems.channelsTest,
checkStore.selectTestItems.timeTest,
checkStore.selectTestItems.test checkStore.selectTestItems.test
], ]
resumeFormal: checkStore.resumeFormal
}).then(res => { }).then(res => {
if (res.code !== 'A0000') { if (res.code !== 'A0000') {
ElMessageBox.alert('守时检测失败', '检测失败', { ElMessageBox.alert('正式检测失败', '检测失败', {
confirmButtonText: '确定', confirmButtonText: '确定',
type: 'error' type: 'error'
}) })
timeTestStatus.value = 'error' TestStatus.value = 'error'
} }
}) })
} }
timeTestStatus.value = 'start'
}
break
case 4: // 正式检测步骤
if (TestStatus.value == 'waiting') {
// 如果没有预检测、系数校准和守时检测,直接进行正式检测需要先初始化
const needStartPreTest =
!checkStore.selectTestItems.preTest &&
!checkStore.selectTestItems.channelsTest &&
!checkStore.selectTestItems.timeTest &&
checkStore.selectTestItems.test
TestStatus.value = 'start' TestStatus.value = 'start'
if (needStartPreTest) {
startPreTest({
userPageId: JwtUtil.getLoginName(),
devIds: deviceIds,
planId: planId,
reCheckType: checkStore.reCheckType == 1 ? '1' : '2',
userId: userStore.userInfo.id,
temperature: checkStore.temperature,
humidity: checkStore.humidity,
testItemList: [
checkStore.selectTestItems.preTest,
checkStore.selectTestItems.channelsTest,
checkStore.selectTestItems.timeTest,
checkStore.selectTestItems.test
],
resumeFormal: checkStore.resumeFormal
}).then(res => {
if (res.code !== 'A0000') {
ElMessageBox.alert(res.message || '正式检测失败', '检测失败', {
confirmButtonText: '确定',
type: 'error'
})
TestStatus.value = 'test_init_fail'
return
}
testRef.value?.startFormalTimer()
}).catch(() => {
TestStatus.value = 'test_init_fail'
})
} else {
testRef.value?.startFormalTimer()
}
} else if (TestStatus.value == 'paused') { } else if (TestStatus.value == 'paused') {
// 如果是暂停状态,发送继续指令 // 如果是暂停状态,发送继续指令
sendResume() sendResume()
@@ -643,14 +566,13 @@ const sendPause = () => {
/** /**
* 发送继续检测指令 * 发送继续检测指令
*/ */
const sendResume = async () => { const sendResume = () => {
// 调用继续检测API // 调用继续检测API
const res: any = await resumeTest({ resumeTest({
userPageId: JwtUtil.getLoginName(), userPageId: JwtUtil.getLoginName(),
devIds: checkStore.devices.map(item => item.deviceId), devIds: checkStore.devices.map(item => item.deviceId),
planId: checkStore.plan.id, planId: checkStore.plan.id,
sourceId: getCurrentSourceId(),
reCheckType: '2', // 操作类型0-系数校验1-预检测2-正式检测8-不合格项复检 reCheckType: '2', // 操作类型0-系数校验1-预检测2-正式检测8-不合格项复检
userId: userStore.userInfo.id, userId: userStore.userInfo.id,
temperature: checkStore.temperature, temperature: checkStore.temperature,
@@ -658,81 +580,11 @@ const sendResume = async () => {
}) })
// 发送继续成功消息给子组件 // 发送继续成功消息给子组件
if (res.code !== 'A0000') {
ElMessage.error(res.message || '继续检测失败')
TestStatus.value = 'paused'
return
}
Object.assign(webMsgSend.value, { Object.assign(webMsgSend.value, {
requestId: 'Resume_Success' 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连接 * 关闭WebSocket连接
*/ */
@@ -777,25 +629,27 @@ const nextStep = () => {
if (ActiveStatue.value != 'error') { if (ActiveStatue.value != 'error') {
ActiveStatue.value = 'waiting' // 重置为等待状态 ActiveStatue.value = 'waiting' // 重置为等待状态
let tempStep = stepsActiveIndex.value let tempStep = stepsActiveIndex.value
const stepOrder = [ let idx = 1
{ key: 'preTest', step: 1 },
{ key: 'channelsTest', step: 2 },
{ key: 'timeTest', step: 3 },
{ key: 'test', step: 4 }
] as const
stepsActiveIndex.value++ // 步骤索引递增 stepsActiveIndex.value++ // 步骤索引递增
// 遍历检测项,找到下一个需要执行的步骤 // 遍历检测项,找到下一个需要执行的步骤
for (let item of stepOrder) { for (let selectTestItemsKey in checkStore.selectTestItems) {
if (tempStep == 0 && checkStore.selectTestItems[item.key]) { if (
tempStep == 0 &&
checkStore.selectTestItems[selectTestItemsKey as keyof typeof checkStore.selectTestItems]
) {
// 找到下一个要执行的检测项 // 找到下一个要执行的检测项
stepsActiveView.value = item.step // 设置显示的组件 stepsActiveView.value = idx // 设置显示的组件
stepsActive.value = item.step // 设置业务逻辑步骤 stepsActive.value = idx // 设置业务逻辑步骤
return return
} }
if (checkStore.selectTestItems[item.key] && tempStep != 0) { if (
checkStore.selectTestItems[selectTestItemsKey as keyof typeof checkStore.selectTestItems] &&
tempStep != 0
) {
tempStep-- // 跳过已选择的检测项 tempStep-- // 跳过已选择的检测项
} }
idx++
} }
} }
} }
@@ -826,18 +680,12 @@ function clearData() {
TestStatus.value = 'waiting' TestStatus.value = 'waiting'
ActiveStatue.value = 'waiting' ActiveStatue.value = 'waiting'
nextStepText.value = '下一步' nextStepText.value = '下一步'
isRestoringFormalProgress.value = false
checkStore.setRestoringFormalProgress(false)
checkStore.setFormalProgressSnapshot(null)
} }
/** /**
* 关闭前确认处理 * 关闭前确认处理
*/ */
const beforeClose = () => { const beforeClose = () => {
if (isRestoringFormalProgress.value) {
return
}
// 如果检测未完成且不是错误状态,需要用户确认 // 如果检测未完成且不是错误状态,需要用户确认
if (stepsActiveIndex.value < stepsTotalNum.value && ActiveStatue.value != 'error') { if (stepsActiveIndex.value < stepsTotalNum.value && ActiveStatue.value != 'error') {
ElMessageBox.confirm('检测未完成,是否退出当前检测流程?', '提示', { ElMessageBox.confirm('检测未完成,是否退出当前检测流程?', '提示', {

View File

@@ -8,12 +8,6 @@
<el-form-item label='相对湿度(%)' prop='humidity' :label-width='110'> <el-form-item label='相对湿度(%)' prop='humidity' :label-width='110'>
<el-input v-model='formContent.humidity' placeholder='请输入湿度' maxlength='32' show-word-limit /> <el-input v-model='formContent.humidity' placeholder='请输入湿度' maxlength='32' show-word-limit />
</el-form-item> </el-form-item>
<el-form-item v-if='hasCheckingDevice' label='是否续检' :label-width='110'>
<el-checkbox v-model='resumeFormalChecked' @change='handleResumeFormalChange' />
</el-form-item>
<el-form-item v-if='checkStore.plan.timeCheck === 1' label='守时检测' prop='timeTest' :label-width='110'>
<el-checkbox v-model='timeTestChecked' @change='handleTimeTestChange' />
</el-form-item>
</el-form> </el-form>
</div> </div>
@@ -39,47 +33,15 @@ const dialogFormRef = ref()
const dialogVisible = ref(false) const dialogVisible = ref(false)
const formContent = reactive<Device.ResTH>({ temperature: 0, humidity: 0 }) const formContent = reactive<Device.ResTH>({ temperature: 0, humidity: 0 })
const hasCheckingDevice = ref(false)
const resumeFormalChecked = ref(false)
const timeTestChecked = ref(false)
const checkStore = useCheckStore() const checkStore = useCheckStore()
const open = async () => { const open = async () => {
resetFormContent() resetFormContent()
syncResumeFormalSelection()
timeTestChecked.value = Boolean(checkStore.selectTestItems.timeTest)
dialogVisible.value = true dialogVisible.value = true
} }
// 清空表单内容 // 清空表单内容
const resetFormContent = () => { const resetFormContent = () => {
Object.assign(formContent, { temperature: '22', humidity: '50' }) Object.assign(formContent, { temperature: '22', humidity: '50' })
hasCheckingDevice.value = false
resumeFormalChecked.value = false
timeTestChecked.value = false
}
const syncTimeTestSelection = (selected: boolean) => {
timeTestChecked.value = selected
checkStore.setSelectTestItems({
...checkStore.selectTestItems,
timeTest: selected,
})
}
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))
} }
@@ -109,7 +71,6 @@ const handleStart = () => {
if (valid) { if (valid) {
checkStore.setTemperature(formContent.temperature) checkStore.setTemperature(formContent.temperature)
checkStore.setHumidity(formContent.humidity) checkStore.setHumidity(formContent.humidity)
checkStore.setResumeFormal(hasCheckingDevice.value && resumeFormalChecked.value)
emit('openTestDialog2') emit('openTestDialog2')
handleClose() handleClose()
} }
@@ -134,4 +95,5 @@ defineExpose({ open })
<style scoped lang='scss'> <style scoped lang='scss'>
</style> </style>

Some files were not shown because too many files have changed in this diff Show More