29 Commits

Author SHA1 Message Date
caozehui
27b593ba01 微调 2026-06-02 11:21:55 +08:00
caozehui
8c7b164166 微调 2026-06-02 09:25:17 +08:00
caozehui
bdc45b8890 微调 2026-06-01 18:37:31 +08:00
caozehui
2705bedc71 微调 2026-06-01 14:12:47 +08:00
caozehui
ae970d048c 微调 2026-06-01 11:34:39 +08:00
caozehui
29f57c80ef 数据库调整 2026-06-01 11:25:37 +08:00
caozehui
80072bf7e0 资源管理前后端 2026-06-01 11:20:11 +08:00
caozehui
8d377dfed7 微调 2026-06-01 11:09:32 +08:00
caozehui
633b6ffd29 自动播放视频 2026-06-01 11:07:47 +08:00
caozehui
cf3141198b 微调 2026-06-01 09:26:01 +08:00
caozehui
c05d329614 补充观看教学视频路由跳转功能,检测页面微调 2026-05-29 10:39:12 +08:00
caozehui
ee08263e4a 检测计划统计弹窗下拉框内容调整 2026-05-29 09:58:24 +08:00
19ea08d5e0 feat(detection): 添加检测锁机制防止多用户同时操作
- 新增 detectionLock store 管理检测锁状态
- 实现检测锁相关的弹窗提示功能
- 添加 DETECTION_BUSY 错误码处理多人竞争逻辑
- 在 websocket 中集成检测锁超时处理
- 修改程序源控制接口以同步锁状态
- 更新项目标题和图标配置
- 添加 docs 目录到忽略列表
2026-05-28 20:44:53 +08:00
caozehui
f9809197e8 微调 2026-05-28 16:33:07 +08:00
caozehui
0090a922c6 资源管理页面微调 2026-05-28 14:37:40 +08:00
caozehui
0b26de20b9 资源管理 2026-05-28 13:26:35 +08:00
caozehui
1202f64bfc 检测计划统计功能 2026-05-28 08:44:15 +08:00
caozehui
ce1738daf0 微调 2026-05-27 11:20:12 +08:00
caozehui
a41d824ca3 归档 2026-05-26 15:45:08 +08:00
caozehui
ac5a8450e8 微调 2026-05-26 14:23:59 +08:00
caozehui
01e817a5d6 微调 2026-05-26 13:45:23 +08:00
caozehui
01bf07fc42 检测计划统计功能 2026-05-26 09:22:38 +08:00
caozehui
633e914c9a Revert "下拉多选报告模版"
This reverts commit 37e69e7bda.
2026-05-25 18:38:46 +08:00
caozehui
37e69e7bda 下拉多选报告模版 2026-05-25 14:25:57 +08:00
caozehui
19fb90432a 归档 2026-05-25 09:51:42 +08:00
caozehui
4a3c81a792 归档 2026-05-25 09:50:16 +08:00
caozehui
12d3073241 统一sourceId 2026-05-13 09:47:10 +08:00
caozehui
72838462ad 微调 2026-04-22 10:02:21 +08:00
caozehui
327addf625 微调 2026-04-22 09:58:03 +08:00
112 changed files with 2004 additions and 235 deletions

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ public/electron/
pnpm-lock.yaml
CLAUDE.md
/public/dist/
/docs/

View File

@@ -33,9 +33,9 @@ mybatis-plus:
#驼峰命名
map-underscore-to-camel-case: true
#配置sql日志输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#关闭日志输出
# log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config:
db-config:
#指定主键生成策略
@@ -55,30 +55,32 @@ webSocket:
#源参数下发,暂态数据默认值
Dip:
#暂态前时间s
fPreTime: 2f
# 暂态前时间s
# fPreTime: 2f
#写入时间s
fRampIn: 0.001f
#写出时间s
fRampOut: 0.001f
#暂态后时间s
fAfterTime: 3f
# 暂态后时间s
# fAfterTime: 3f
Flicker:
waveFluType: CPM
waveType: SQU
fDutyCycle: 50f
#Flicker:
# waveFluType: CPM
# waveType: SQU
# fDutyCycle: 50f
log:
homeDir: {{APP_DATA_PATH}}\logs
commonLevel: info
#log:
# homeDir: D:\logs
# commonLevel: info
report:
template: {{APP_DATA_PATH}}\template
reportDir: {{APP_DATA_PATH}}\report
# template: D:\template
# reportDir: D:\report
dateFormat: yyyy年MM月dd日
data:
homeDir: {{APP_DATA_PATH}}\data
#data:
# homeDir: D:\data
#resource:
# videoDir: ${data.homeDir}\resources\videos
qr:
cloud: http://pqmcc.com:18082/api/file
dev:

View File

@@ -1 +1 @@
11900
116212

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -11,3 +11,8 @@
.\binlog.000033
.\binlog.000034
.\binlog.000035
.\binlog.000036
.\binlog.000037
.\binlog.000038
.\binlog.000039
.\binlog.000040

View File

@@ -19,9 +19,10 @@ VITE_API_URL=/api
# 开发环境跨域代理,支持配置多个
VITE_PROXY=[["/api","http://127.0.0.1:18093/"]]
VITE_PROXY=[["/api","http://127.0.0.1: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.1.138:8080/"]]张文
VITE_IS_SHOW_RAW_DATA=true
# 开启激活验证
VITE_ACTIVATE_OPEN=true
VITE_ACTIVATE_OPEN=false

View File

@@ -23,6 +23,7 @@ VITE_PWA=true
# 线上环境接口地址
#VITE_API_URL="/api" # 打包时用
VITE_API_URL="http://127.0.0.1:18093/"
VITE_API_URL="http://127.0.0.1:18092/"
VITE_IS_SHOW_RAW_DATA=true
# 开启激活验证
VITE_ACTIVATE_OPEN=true
VITE_ACTIVATE_OPEN=false

View File

@@ -4,7 +4,8 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" />
<title></title>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<title>NPQS-9100</title>
<!-- 优化vue渲染未完成之前先加一个css动画 -->
<style>
#loadingPage {

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,15 @@
import http from '@/api'
import type { DetectionLockHolder } from '@/stores/modules/detectionLock'
/**
* 查询当前检测锁持有状态
* - data 为 null → 锁空闲
* - data 非 null → 锁被某账号持有
*
* 本接口只读,不抢锁、不会返回 DETECTION_BUSY
*/
export const getCurrentLock = () => {
return http.get<DetectionLockHolder | null>('/detection/lock/current', undefined, {
loading: false
})
}

View File

@@ -1,6 +1,7 @@
import type { controlSource } from '@/api/device/interface/controlSource'
import http from '@/api'
import { useDetectionLockStore } from '@/stores/modules/detectionLock'
/**
* @name 程控源管理模块
@@ -17,8 +18,11 @@ export const startSimulateTest = (params: controlSource.ResControl) => {
}
//停止
export const closeSimulateTest = (params: controlSource.ResControl) => {
return http.post(`/prepare/closeSimulateTest`,params,{loading:false})
export const closeSimulateTest = async (params: controlSource.ResControl) => {
const result = await http.post(`/prepare/closeSimulateTest`,params,{loading:false})
// 主动终止 → 释放本地持锁标记
useDetectionLockStore().clearHolder()
return result
}

View File

@@ -2,43 +2,44 @@ import type { ReqPage, ResPage } from '@/api/interface'
// 检测源模块
export namespace TestSource {
/**
* 检测脚本表格分页查询参数
*/
export interface ReqTestSourceParams extends ReqPage{
id: string; // 装置序号id 必填
name: string;
pattern: string;
}
/**
* 检测源表格分页查询参数
*/
export interface ReqTestSourceParams extends ReqPage {
id: string
name: string
pattern: string
}
// 检测源接口
export interface ResTestSource {
id: string; //检测源ID
name?: string; //检测源名称(检测源类型 + 设备类型 + 数字自动生成)
pattern: string;//检测源模式(字典表Code字段数字、模拟、比对)
type: string; //检测源类型(字典表Code字段标准源、高精度设备)
devType: string;//检测源设备类型(字典表Code字段)
parameter?: string;//源参数JSON字符串
state:number;//
createBy?: string;
createTime?: string;
updateBy?: string;
updateTime?: string;
id: string
name?: string
pattern: string
type: string
devType: string
maxVoltage?: number
maxCurrent?: number
parameter?: string
state: number
createBy?: string
createTime?: string
updateBy?: string
updateTime?: string
}
/* 检测脚本查询分页返回的对象;
*/
export interface ResTestSourcePage extends ResPage<ResTestSource> {
/*
* 检测源查询分页返回的对象
*/
export interface ResTestSourcePage extends ResPage<ResTestSource> {}
}
export interface ParameterType{
id:string;
type:string;
desc:string;
value:string|null;
sort:number;
pId:string;
children?:ParameterType[];
export interface ParameterType {
id: string
type: string
desc: string
value: string | null
sort: number
pId: string
children?: ParameterType[]
}
}
}

View File

@@ -12,6 +12,12 @@ import { type ResultData } from '@/api/interface'
import { ResultEnum } from '@/enums/httpEnum'
import { checkStatus } from './helper/checkStatus'
import { useUserStore } from '@/stores/modules/user'
import { useDetectionLockStore, type DetectionLockHolder } from '@/stores/modules/detectionLock'
import {
showForceReleasedDialog,
showLockBusyDialog,
showLockNotStartedToast
} from '@/utils/detectionLockDialog'
import router from '@/routers'
import { refreshToken } from '@/api/user/login'
import { EventSourcePolyfill } from 'event-source-polyfill'
@@ -107,6 +113,32 @@ class RequestHttp {
}
return Promise.reject(data)
}
// 单用户检测互斥:命中 DETECTION_BUSY 时根据 data 和本地持锁状态分发到 4 种文案
if (data.code === ResultEnum.DETECTION_BUSY) {
const lockStore = useDetectionLockStore()
const holder = (data.data ?? null) as DetectionLockHolder | null
const currentUserId = userStore.userInfo?.id
const localDetecting = lockStore.iAmHolder
if (!localDetecting && holder) {
// S1:他人持锁
showLockBusyDialog(holder)
} else if (!localDetecting && !holder) {
// S2:未开始检测就调中间接口
showLockNotStartedToast()
} else if (localDetecting && holder && holder.holderUserId !== currentUserId) {
// S4-a:被强释 + 别人接手
showForceReleasedDialog(holder)
lockStore.clearHolder()
} else if (localDetecting && !holder) {
// S4-b:被强释,无人接手
showForceReleasedDialog(null)
lockStore.clearHolder()
}
// 阻断默认错误提示,不再走下面的 ElMessage.error
return Promise.reject(data)
}
// 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
if (data.code && data.code !== ResultEnum.SUCCESS) {
if (data.message.includes('&')) {

View File

@@ -69,5 +69,37 @@ export namespace Plan {
maxTime: number;
}
export interface PlanStatisticsItem {
itemId: string;
itemName: string;
unqualifiedCount: number;
}
export interface PlanStatisticsOption {
id: string;
name: string;
}
export interface PlanStatistics {
planId: string;
planName: string;
totalCheckCount: number;
checkedDeviceCount: number;
uncheckedDeviceCount: number;
firstQualifiedDeviceCount: number;
secondQualifiedDeviceCount: number;
thirdOrMoreQualifiedDeviceCount: number;
qualifiedDeviceCount: number;
unqualifiedDeviceCount: number;
unqualifiedItemCount: number;
firstPassRate: number;
secondPassRate: number;
thirdOrMorePassRate: number;
unqualifiedRate: number;
itemDistributions: PlanStatisticsItem[];
manufacturerOptions: PlanStatisticsOption[];
devTypeOptions: PlanStatisticsOption[];
}
}

View File

@@ -94,6 +94,10 @@ export const staticsAnalyse = (params: { id: string[] }) => {
return http.download('/adPlan/analyse', params)
}
export const getPlanStatistics = (params: { planId: string; manufacturer?: string; devType?: string }) => {
return http.post<Plan.PlanStatistics>(`/adPlan/statistics`, params)
}
//根据计划id分页查询被检设
export const getDevListByPlanId = (params: any) => {
return http.post(`/adPlan/listDevByPlanId`, params)

View File

@@ -0,0 +1,18 @@
import http from '@/api'
import type { ResourceManage } from '@/api/resourceManage/interface'
export const getResourceManageList = (params: ResourceManage.ReqResourceManageParams) => {
return http.post<ResourceManage.ResResourceManagePage>('/resourceManage/list', params)
}
export const addResourceManage = (params: FormData) => {
return http.upload('/resourceManage/add', params)
}
export const updateResourceManage = (params: ResourceManage.ReqUpdateResourceManage) => {
return http.post('/resourceManage/update', params)
}
export const getResourceManagePlayUrl = (id: string) => {
return http.get<ResourceManage.PlayVO>(`/resourceManage/play?id=${id}`)
}

View File

@@ -0,0 +1,34 @@
import type { ReqPage, ResPage } from '@/api/interface'
export namespace ResourceManage {
export interface ReqResourceManageParams extends ReqPage {
name?: string
fileName?: string
}
export interface ResResourceManage {
id: string
name: string
fileName: string
fileSize: number
relativePath: string
remark: string
state: number
createBy?: string | null
createTime?: string | null
updateBy?: string | null
updateTime?: string | null
}
export interface ResResourceManagePage extends ResPage<ResResourceManage> {}
export interface ReqUpdateResourceManage {
id: string
name: string
remark: string
}
export interface PlayVO {
url: string
}
}

View File

@@ -1,8 +1,12 @@
import http from '@/api'
import { useDetectionLockStore } from '@/stores/modules/detectionLock'
export const startPreTest = (params) => {
return http.post(`/prepare/startPreTest`, params, {loading: false})
export const startPreTest = async (params) => {
const result = await http.post(`/prepare/startPreTest`, params, {loading: false})
// 抢锁成功 → 标记本地为持锁者
useDetectionLockStore().setAsHolder()
return result
}
export const closePreTest = (params) => {
@@ -37,8 +41,11 @@ export const resumeTest = (params) => {
* 比对式通道配对
* @param params
*/
export const contrastTest = (params: any) => {
return http.post(`/prepare/startContrastTest`,params)
export const contrastTest = async (params: any) => {
const result = await http.post(`/prepare/startContrastTest`, params)
// 抢锁成功 → 标记本地为持锁者
useDetectionLockStore().setAsHolder()
return result
}
export const exportAlignData= () => {

View File

@@ -0,0 +1,9 @@
import http from '@/api'
export const startSntpService = () => {
return http.post('/sntp/start', {})
}
export const stopSntpService = () => {
return http.post('/sntp/stop', {})
}

View File

@@ -6,6 +6,7 @@ export enum ResultEnum {
ERROR = 500,
ACCESSTOKEN_EXPIRED = "A0024",
OVERDUE = "A0025",
DETECTION_BUSY = "A020042",
TIMEOUT = 30000,
TYPE = "success"
}

View File

@@ -19,3 +19,6 @@ export const DICT_STORE_KEY = "cn-dictData";
export const CHECK_STORE_KEY = "cn-check";
// pinia中detectionLock store的key
export const DETECTION_LOCK_STORE_KEY = "cn-detectionLock";

View File

@@ -0,0 +1,33 @@
import { defineStore } from 'pinia'
import { DETECTION_LOCK_STORE_KEY } from '@/stores/constant'
export interface DetectionLockHolder {
holderUserId: string
holderUserName: string
acquireTime: string
expireAt: string
}
export const useDetectionLockStore = defineStore(DETECTION_LOCK_STORE_KEY, {
state: () => ({
iAmHolder: false,
holder: null as DetectionLockHolder | null
}),
actions: {
setAsHolder(holder?: Partial<DetectionLockHolder> | null) {
this.iAmHolder = true
if (holder) {
this.holder = {
holderUserId: holder.holderUserId ?? '',
holderUserName: holder.holderUserName ?? '',
acquireTime: holder.acquireTime ?? '',
expireAt: holder.expireAt ?? ''
}
}
},
clearHolder() {
this.iAmHolder = false
this.holder = null
}
}
})

View File

@@ -0,0 +1,85 @@
import { ElMessage, ElMessageBox } from 'element-plus'
import router from '@/routers'
import type { DetectionLockHolder } from '@/stores/modules/detectionLock'
import mittBus, { STOP_DETECTION_TIMER_EVENT } from '@/utils/mittBus'
import { requestResourceManageAutoplayFirst } from '@/utils/resourceManageAutoplay'
const stopDetectionTimer = () => {
mittBus.emit(STOP_DETECTION_TIMER_EVENT)
}
const goResourceManage = async () => {
requestResourceManageAutoplayFirst()
if (router.hasRoute('resourceManage')) {
await router.push({ name: 'resourceManage' })
return
}
await router.push('/resourceManage')
}
/**
* S1: 他人正在做检测, 自己抢锁被挡
*/
export const showLockBusyDialog = (holder: DetectionLockHolder) => {
stopDetectionTimer()
ElMessageBox.confirm(`${holder.holderUserName}」正在做检测,请稍后。`, '检测进行中', {
confirmButtonText: '观看检测视频教学',
cancelButtonText: '我知道了',
type: 'warning',
distinguishCancelAndClose: true,
customClass: 'detection-lock-busy-dialog'
})
.then(() => {
return goResourceManage()
})
.catch(() => {
// 用户点了"我知道了"或关闭,什么都不做
})
}
/**
* S2: 未开始检测就调中间接口
*/
export const showLockNotStartedToast = () => {
ElMessage.warning('请先点击"开始检测"按钮启动本轮检测')
}
/**
* S3: 自己暂停超 10 分钟, 被 WS 推 STOP_TIMEOUT 强制结束
*/
export const showPauseTimeoutDialog = () => {
stopDetectionTimer()
ElMessageBox.alert('暂停超过 10 分钟未恢复,系统已自动结束本次检测。\n\n如需继续,请重新发起检测。', '本次检测已结束', {
confirmButtonText: '我知道了',
type: 'warning'
}).catch(() => {})
}
/**
* S4: 被管理员强制释放
* - holder 为 null: 强释后无人接手
* - holder 非 null: 强释后被别人立刻抢占
*/
export const showForceReleasedDialog = (holder: DetectionLockHolder | null) => {
stopDetectionTimer()
if (holder) {
ElMessageBox.confirm(`当前「${holder.holderUserName}」正在做检测,您无法继续检测,请稍后。`, '检测已被中止', {
confirmButtonText: '观看检测视频教学',
cancelButtonText: '我知道了',
type: 'warning',
distinguishCancelAndClose: true
})
.then(() => {
return goResourceManage()
})
.catch(() => {})
} else {
ElMessageBox.alert('您的检测已被管理员强制结束。\n如需继续,请重新发起检测。', '检测已被中止', {
confirmButtonText: '我知道了',
type: 'warning'
}).catch(() => {})
}
}

View File

@@ -1,4 +1,11 @@
import mitt from "mitt";
const mittBus = mitt();
export const STOP_DETECTION_TIMER_EVENT = "stopDetectionTimer";
type MittBusEvents = {
openThemeDrawer: undefined;
[STOP_DETECTION_TIMER_EVENT]: undefined;
};
const mittBus = mitt<MittBusEvents>();
export default mittBus;

View File

@@ -0,0 +1,13 @@
let shouldAutoplayFirstVideo = false
export const requestResourceManageAutoplayFirst = () => {
shouldAutoplayFirstVideo = true
}
export const hasPendingResourceManageAutoplayFirst = () => shouldAutoplayFirstVideo
export const consumeResourceManageAutoplayFirst = () => {
if (!shouldAutoplayFirstVideo) return false
shouldAutoplayFirstVideo = false
return true
}

View File

@@ -9,6 +9,8 @@
import { ElMessage } from "element-plus";
import { jwtUtil } from "./jwtUtil";
import { useDetectionLockStore } from "@/stores/modules/detectionLock";
import { showPauseTimeoutDialog } from "@/utils/detectionLockDialog";
// ============================================================================
// 类型定义 (Types & Interfaces)
@@ -190,8 +192,8 @@ export default class SocketService {
* WebSocket连接配置
*/
private config: SocketConfig = {
// url: 'ws://127.0.0.1:7778/hello',
url: 'ws://127.0.0.1:7778/hello',
//url: 'ws://192.168.1.124:7777/hello',
heartbeatInterval: 9000, // 9秒心跳间隔
reconnectDelay: 5000, // 5秒重连延迟
maxReconnectAttempts: 5, // 最多重连5次
@@ -546,6 +548,18 @@ export default class SocketService {
// 检查是否为JSON格式
if (typeof event.data === 'string' && (event.data.startsWith('{') || event.data.startsWith('['))) {
const message: WebSocketMessage = JSON.parse(event.data);
// 全局拦截:暂停 10 分钟超时,后端推 STOP_TIMEOUT 表示锁已被释放
// 不管页面是否注册了对应回调,都要做"清本地持锁标记 + 弹窗告知"
if (message?.operateCode === 'STOP_TIMEOUT') {
try {
useDetectionLockStore().clearHolder();
showPauseTimeoutDialog();
} catch (e) {
console.error('STOP_TIMEOUT 全局处理失败:', e);
}
}
if (message?.type && this.callBackMapping[message.type]) {
this.callBackMapping[message.type](message);
} else {

View File

@@ -18,7 +18,7 @@
<template #operation='scope'>
<el-button v-auth.role="'edit'" type='primary' link :icon='EditPen' @click="openDrawer('edit', scope.row)" :disabled="scope.row.code == 'root'">编辑</el-button>
<el-button v-auth.role="'delete'" type='primary' link :icon='Delete' @click='deleteAccount(scope.row)' :disabled="scope.row.code == 'root'">删除</el-button>
<el-button v-auth.role="'SetPermissions'" type='primary' link :icon='Share' @click="openDrawer('设置权限', scope.row)" :disabled="scope.row.code == 'root'">设置权限</el-button>
<el-button v-auth.role="'SetPermissions'" type='primary' link :icon='Share' @click="openDrawer('设置权限', scope.row)" >设置权限</el-button>
</template>
</ProTable>

View File

@@ -122,7 +122,8 @@
<script lang="tsx" setup name="test">
import {InfoFilled, Loading} from '@element-plus/icons-vue'
import CompareDataCheckSingleChannelSingleTestPopup from './compareDataCheckSingleChannelSingleTestPopup.vue'
import {computed, ComputedRef, nextTick, onBeforeMount, onMounted, reactive, ref, toRef, watch} from 'vue'
import {computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, toRef, watch} from 'vue'
import type { ComputedRef } from 'vue'
import {dialogBig} from '@/utils/elementBind'
import {CheckData} from '@/api/check/interface'
import {useCheckStore} from '@/stores/modules/check'
@@ -132,6 +133,7 @@ import {getAutoGenerate, getCanCoefficient, startCoefficient} from '@/api/user/l
import { generateDevReport } from '@/api/plan/plan'
import {useModeStore} from '@/stores/modules/mode' // 引入模式 store
import {useDictStore} from '@/stores/modules/dict'
import mittBus, { STOP_DETECTION_TIMER_EVENT } from '@/utils/mittBus'
const checkStore = useCheckStore()
const modeStore = useModeStore()
const dictStore = useDictStore()
@@ -740,6 +742,10 @@ const stopTimeCount = (type: number) => {
}
}
const handleStopDetectionTimer = () => {
stopTimeCount(1)
}
// 将秒数转换为 HH:MM:SS 格式
const secondToTime = (second: number) => {
let h: string | number = Math.floor(second / 3600) // 小时
@@ -898,6 +904,7 @@ const initializeParameters = async () => {
//
onMounted(() => {
mittBus.on(STOP_DETECTION_TIMER_EVENT, handleStopDetectionTimer)
if (!checkStore.selectTestItems.preTest) {
// 判断是否预检测
@@ -905,6 +912,10 @@ onMounted(() => {
}
})
onBeforeUnmount(() => {
mittBus.off(STOP_DETECTION_TIMER_EVENT, handleStopDetectionTimer)
})
defineExpose({
initializeParameters,
handlePause,

View File

@@ -148,6 +148,7 @@ import { useCheckStore } from '@/stores/modules/check'
import { contrastTest, pauseTest, resumeTest, 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'
const userStore = useUserStore()
const checkStore = useCheckStore()
@@ -165,6 +166,14 @@ const preTestStatus = ref('waiting') //预检测执行状态
const TestStatus = ref('waiting') //正式检测执行状态
const webMsgSend = ref() //webSocket推送的数据
const hideInitializingButton = () => {
if (TestStatus.value === 'test_init') {
TestStatus.value = 'waiting'
}
}
mittBus.on(STOP_DETECTION_TIMER_EVENT, hideInitializingButton)
const dialogTitle = ref('')
const showComponent = ref(true)
const preTestRef = ref<InstanceType<typeof ComparePreTest> | null>(null)
@@ -187,6 +196,7 @@ onMounted(() => {
})
onUnmounted(() => {
mittBus.off(STOP_DETECTION_TIMER_EVENT, hideInitializingButton)
window.removeEventListener('resize', handleResize)
})

View File

@@ -206,6 +206,23 @@ function handleWarningError(stepRef: any, logRef: any, message: string) {
watch(webMsgSend, function (newValue, oldValue) {
if (testStatus.value !== 'waiting') {
switch (newValue.requestId) {
case 'overloadTest':
if (newValue.code === 1) {
handleFatalError(step1, step1InitLog, '电压过载!')
}
if (newValue.code === 2) {
handleFatalError(step1, step1InitLog, '电流过载!')
}
if (newValue.code === 3) {
handleFatalError(step1, step1InitLog, '电压和电流过载!')
}
if (newValue.code === 4) {
step1InitLog.value.push({
type: 'info',
log: '过载测试成功!',
})
}
break;
case 'yjc_ytxjy':
switch (newValue.operateCode) {
case 'INIT_GATHER':

View File

@@ -91,7 +91,7 @@
type="primary"
icon="Clock"
@click="handleTest('手动检测')"
v-if="form.activeTabs === 0 && modeStore.currentMode == '模拟式'"
v-if="form.activeTabs === 0 && modeStore.currentMode != '比对式'"
>
手动检测
</el-button>
@@ -483,7 +483,7 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
sortable: true,
isShow: checkStateShow,
render: scope => {
return scope.row.checkState === 0 ? '未检' : scope.row.checkState === 1 ? '检测中' : '检测完成'
return scope.row.checkState === 0 ? '未检' : scope.row.checkState === 1 ? '检测中' : scope.row.checkState === 2 ? '检测完成':'归档'
}
},
{
@@ -494,10 +494,12 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
render: scope => {
if (scope.row.checkResult === 0) {
return <el-tag type="danger">不符合</el-tag>
} else if (scope.row.checkResult === 0) {
return '不符合'
} else if (scope.row.checkResult === 1) {
return '符合'
} else if (scope.row.checkResult === 2) {
return '未检'
}else if(scope.row.checkResult === 2) {
return '未检'
}
return ''
}
@@ -539,7 +541,6 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
{ prop: 'operation', label: '操作', fixed: 'right', minWidth :200,isShow: operationShow }
])
let testType = 'test' // 检测类型:'test'-检测 'reTest'-复检
let qualifiedCount = 0 //合格数量
//比对单个报告生成
@@ -575,8 +576,6 @@ const handleSelectionChange = (selection: any[]) => {
} else {
testType = 'reTest'
}
qualifiedCount=selection.filter(item => item.checkResult == 1).length
let devices: CheckData.Device[] = selection.map((item: any) => {
return {
deviceId: item.id,
@@ -599,6 +598,19 @@ 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 canUseUnqualifiedItemRecheck = () => hasCheckedUnqualifiedSelectedDevice() && !hasUncheckedSelectedDevice()
//查询
const handleSearch = () => {
proTable.value?.getTableList()
@@ -923,12 +935,12 @@ const handleTest = async (val: string) => {
dialogTitle.value = val
if (val === '手动检测') {
checkStore.setShowDetailType(2)
if (testType === 'reTest') {
if (shouldShowRecheckModeDialog()) {
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
distinguishCancelAndClose: true,
confirmButtonText: '不合格项复检',
cancelButtonText: '全部复检',
showConfirmButton:qualifiedCount<=0,
showConfirmButton: canUseUnqualifiedItemRecheck(),
type: 'warning'
})
.then(() => {
@@ -963,11 +975,12 @@ const handleTest = async (val: string) => {
checkStore.setCheckType(1)
checkStore.initSelectTestItems()
// 一键检测
if (testType === 'reTest' && modeStore.currentMode != '比对式') {
if (shouldShowRecheckModeDialog() && modeStore.currentMode != '比对式') {
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
distinguishCancelAndClose: true,
confirmButtonText: '不合格项复检',
cancelButtonText: '全部复检',
showConfirmButton: canUseUnqualifiedItemRecheck(),
type: 'warning'
})
.then(() => {
@@ -1087,7 +1100,7 @@ const openDrawer = async (title: string, row: any) => {
if (title === '检测数据查询') {
checkStore.setShowDetailType(0)
if (modeStore.currentMode == '模拟式') {
if (modeStore.currentMode == '模拟式'||modeStore.currentMode == '数字式') {
dataCheckPopupRef.value?.open(row.id, '-1', null)
} else if (modeStore.currentMode == '比对式') {
dataCheckSingleChannelSingleTestPopupRef.value?.open(row, null, row.id, 2)
@@ -1095,7 +1108,7 @@ const openDrawer = async (title: string, row: any) => {
}
if (title === '误差体系更换') {
checkStore.setShowDetailType(1)
if (modeStore.currentMode == '模拟式') {
if (modeStore.currentMode == '模拟式'||modeStore.currentMode == '数字式') {
dataCheckPopupRef.value?.open(row.id, '-1', null)
} else if (modeStore.currentMode == '比对式') {
dataCheckSingleChannelSingleTestPopupRef.value?.open(row, null, row.id, 2)

View File

@@ -105,7 +105,7 @@ import { InfoFilled, Loading } from '@element-plus/icons-vue'
// 单通道单测试项详情弹窗组件
import dataCheckSingleChannelSingleTestPopup from './dataCheckSingleChannelSingleTestPopup.vue'
// Vue 3 Composition API
import { computed, reactive, ref, toRef, watch } from 'vue'
import { computed, onBeforeUnmount, onMounted, reactive, ref, toRef, watch } from 'vue'
// 对话框大小绑定工具
import { dialogBig } from '@/utils/elementBind'
// 检测数据类型定义
@@ -120,6 +120,7 @@ import { getAutoGenerate } from '@/api/user/login'
import { generateDevReport } from '@/api/plan/plan'
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
import { useDictStore } from '@/stores/modules/dict'
import mittBus, { STOP_DETECTION_TIMER_EVENT } from '@/utils/mittBus'
// 获取检测状态管理实例
const checkStore = useCheckStore()
@@ -1176,6 +1177,10 @@ const stopTimeCount = () => {
}
}
const handleStopDetectionTimer = () => {
stopTimeCount()
}
// 恢复计时(用于暂停后继续)
const resumeTimeCount = () => {
@@ -1199,8 +1204,14 @@ const secondToTime = (second: number) => {
return h + ':' + m + ':' + s
}
onMounted(() => {
mittBus.on(STOP_DETECTION_TIMER_EVENT, handleStopDetectionTimer)
})
// 组件卸载前清理定时器和响应式引用
onBeforeUnmount(() => {
mittBus.off(STOP_DETECTION_TIMER_EVENT, handleStopDetectionTimer)
// 清理定时器
if (timer) {
clearInterval(timer)

View File

@@ -172,6 +172,7 @@ import { useCheckStore } from '@/stores/modules/check'
import { pauseTest, resumeTest, 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'
// ====================== 状态管理 ======================
const userStore = useUserStore()
@@ -200,6 +201,14 @@ const channelsTestStatus = ref('waiting') // 通道系数校准执行状态
const TestStatus = ref('waiting') // 正式检测执行状态
const webMsgSend = ref() // webSocket推送的数据用于组件间通信
const hideInitializingButton = () => {
if (TestStatus.value === 'test_init') {
TestStatus.value = 'waiting'
}
}
mittBus.on(STOP_DETECTION_TIMER_EVENT, hideInitializingButton)
// ====================== WebSocket 相关 ======================
const dataSocket = reactive<{
socketServe: typeof socketClient.Instance | null
@@ -705,6 +714,7 @@ const handleClose = () => {
* 确保路由切换或组件销毁时正确关闭WebSocket连接
*/
onBeforeUnmount(() => {
mittBus.off(STOP_DETECTION_TIMER_EVENT, hideInitializingButton)
closeWebSocket() // 组件销毁前关闭WebSocket连接
})

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