Files
pqs-9100_client/frontend/src/views/home/components/compareTest.vue
2025-08-26 18:29:14 +08:00

750 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<div class="dialog" v-bind="dialogBig">
<div class="dialog-title">
<div class="timeView">
<el-icon style="margin: 0px 5px">
<Clock />
</el-icon>
<span>检测用时{{ timeView }}</span>
</div>
<el-progress style="width: 82%; margin-right: 3%" :percentage="percentage" :color="customColors" />
<el-button style="width: 10%" type="text" :icon="InfoFilled" @click="showTestLog">检测项进度</el-button>
</div>
<div class="dialog-content">
<el-table
:data="checkResultView"
row-key="scriptType"
height="450px"
:header-cell-style="{ background: 'var(--el-color-primary)', color: '#eee', textAlign: 'center' }"
style="width: 100%"
border
>
<el-table-column
fixed
prop="scriptName"
label="检测项目"
width="150px"
align="center"
></el-table-column>
<template v-if="chnSum <= MAX_CHN_SUM">
<el-table-column
v-for="(item, index1) in deviceList"
:key="item.deviceId"
:label="item.deviceName"
:min-width="110"
align="center"
>
<el-table-column
v-for="(chnItem, index2) in checkStore.chnNumList"
:key="`${item.deviceId}${chnItem}`"
:label="'通道' + chnItem"
align="center"
>
<template #default="{ row }">
<el-tooltip
:content="
row.devices[index1].chnResult[index2].icon === 'More'
? '暂无数据'
: row.devices[index1].chnResult[index2].icon === 'CircleCheckFilled'
? '符合'
: row.devices[index1].chnResult[index2].icon === 'Close'
? '不符合'
: row.devices[index1].chnResult[index2].icon ===
'WarnTriangleFilled'
? '数据异常'
: row.devices[index1].chnResult[index2].icon === 'Loading'
? '检测中'
: '连接中断'
"
placement="right"
>
<el-button
:disabled="
row.devices[index1].chnResult[index2].color ===
CheckData.ButtonColorEnum.INFO ||
row.devices[index1].chnResult[index2].color ===
CheckData.ButtonColorEnum.LOADING
"
:color="row.devices[index1].chnResult[index2].color"
size="small"
@click="handleClick(row, chnItem, row.scriptType)"
style="align-self: center"
>
<el-icon
v-if="row.devices[index1].chnResult[index2].icon === 'Loading'"
class="loading-box"
style="color: #fff"
>
<component :is="Loading" />
</el-icon>
<el-icon v-else style="color: #fff">
<component :is="row.devices[index1].chnResult[index2].icon" />
</el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
</template>
</el-table>
</div>
</div>
<div class="drawer-container">
<el-drawer v-model="drawer" title="检测项进度" direction="btt" :size="'38%'">
<div ref="scrollContainerRef" style="height: 100%; overflow-y: auto">
<p
v-for="(item, index) in testLogList"
:key="index"
:style="{
color:
item.type === 'error'
? '#F56C6C'
: item.type === 'warning'
? '#e6a23c'
: 'var(--el-text-color-regular)'
}"
>
{{ item.log }}
<br />
</p>
</div>
</el-drawer>
</div>
<CompareDataCheckSingleChannelSingleTestPopup
ref="dataCheckSingleChannelSingleTestPopupRef"
:append-to-body="true"
/>
</div>
</template>
<script lang="tsx" setup name="test">
import { InfoFilled, Loading } from '@element-plus/icons-vue'
import CompareDataCheckSingleChannelSingleTestPopup from './compareDataCheckSingleChannelSingleTestPopup.vue'
import { computed, ComputedRef, onBeforeMount, reactive, ref, toRef, watch, onMounted } from 'vue'
import { dialogBig } from '@/utils/elementBind'
import { CheckData } from '@/api/check/interface'
import { useCheckStore } from '@/stores/modules/check'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getBigTestItem } from '@/api/check/test'
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'
const checkStore = useCheckStore()
const modeStore = useModeStore()
const dictStore = useDictStore()
// 最大通道数
const MAX_CHN_SUM = 12
// 总测试项数
let checkTotal = 0
const props = defineProps({
testStatus: {
type: String,
default: 'waiting'
},
stepsActive: {
type: Number
},
webMsgSend: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits([
'update:testStatus',
'update:webMsgSend',
'sendPause',
'sendResume',
'sendReCheck',
'closeWebSocket'
])
// 用来保存测试项进度抽屉是否打开
const drawer = ref(false)
// 进度条颜色
const customColors = [{ color: '#91cc75', percentage: 100 }]
// 检测脚本数据
let scriptData: CheckData.ScriptItem[] = []
// 用来保存被检设备
const deviceList = reactive<CheckData.Device[]>([])
// 当前进行的测试项索引
let activeIndex = 0
// 百分比
const percentage = ref(0)
// 时间计数器
let timer: any = null
const timeCount = ref(0)
const timeView = ref('00:00:00')
//测试项开始检测时间(或继续检测时间)
const startData = ref(new Date())
//测试项检测结束时间(或暂停时的时间)
const endData = ref(new Date())
const timeDifference = ref(0)
// 真正的检测结果(详细到通道)
const checkResult = reactive<CheckData.ScriptChnItem[]>([])
// 用来存放检测出现失败的测试项id。只要有一个通道检测不合格则该检测项的id会被加入该数组。
let errorCheckItem: Array<{ scriptType: string; type: CheckData.ChnCheckResultEnum }> = []
// 用来存放检测日志
const testLogList = reactive<CheckData.LogItem[]>([{ type: 'info', log: '暂无数据,等待检测开始' }])
const testStatus = toRef(props, 'testStatus')
const webMsgSend = toRef(props, 'webMsgSend')
const scrollContainerRef = ref()
const dataCheckSingleChannelSingleTestPopupRef =
ref<InstanceType<typeof CompareDataCheckSingleChannelSingleTestPopup>>()
// 总通道数
const chnSum = computed(() => {
let sum = 0
deviceList.forEach(item => {
sum += item.chnNum
})
return sum
})
// 用来展示的检测结果
const checkResultView: ComputedRef<CheckData.ScriptChnViewItem[]> = computed(() => {
let result: CheckData.ScriptChnViewItem[] = checkResult.map(item => {
let temp: CheckData.ScriptChnViewItem = {
scriptType: item.scriptType,
scriptName: item.scriptName,
devices: []
}
item.devices.forEach(device => {
console.log('🚀 ~ item:', item, CheckData.ChnCheckResultEnum)
let tempChnBtnResult: CheckData.ButtonResult[] = []
if (chnSum.value <= MAX_CHN_SUM) {
for (let j = 0; j < device.chnResult.length; j++) {
switch (device.chnResult[j]) {
case CheckData.ChnCheckResultEnum.UNKNOWN:
tempChnBtnResult.push({ color: CheckData.ButtonColorEnum.INFO, icon: 'More' })
break
case CheckData.ChnCheckResultEnum.LOADING:
tempChnBtnResult.push({ color: CheckData.ButtonColorEnum.LOADING, icon: 'Loading' })
break
case CheckData.ChnCheckResultEnum.SUCCESS:
tempChnBtnResult.push({
color: CheckData.ButtonColorEnum.SUCCESS,
icon: 'CircleCheckFilled'
})
break
case CheckData.ChnCheckResultEnum.FAIL:
tempChnBtnResult.push({ color: CheckData.ButtonColorEnum.DANGER, icon: 'Close' })
break
case CheckData.ChnCheckResultEnum.TIMEOUT:
tempChnBtnResult.push({ color: CheckData.ButtonColorEnum.WARNING, icon: 'Link' })
break
case CheckData.ChnCheckResultEnum.ERRORDATA:
tempChnBtnResult.push({
color: CheckData.ButtonColorEnum.WARNING,
icon: 'WarnTriangleFilled'
})
break
case CheckData.ChnCheckResultEnum.NOT_PART_IN_ERROR:
tempChnBtnResult.push({ color: CheckData.ButtonColorEnum.INFO, icon: 'Minus' })
break
default:
break
}
}
}
temp.devices.push({
deviceId: device.deviceId,
deviceName: device.deviceName,
chnResult: tempChnBtnResult
})
})
return temp
})
return result
})
watch(testStatus, function (newValue, oldValue) {
if (newValue == 'start' || newValue == 'waiting') {
if (!checkStore.selectTestItems.preTest && !checkStore.selectTestItems.channelsTest) {
ElMessage.success('初始化开始!')
emit('update:testStatus', 'test_init')
if (checkStore.selectTestItems.test && checkStore.selectTestItems.preTest) {
testLogList.push({ type: 'info', log: `${new Date().toLocaleString()}:初始化开始!` })
}
} else {
emit('update:testStatus', 'process')
}
// 开始计时
startTimeCount()
showTestLog()
setTimeout(() => {
initCheckResult(CheckData.ChnCheckResultEnum.LOADING)
}, 500)
//startTimer() // todo 可移除
startData.value = new Date()
timeDifference.value = 0
} else if (newValue == 'error') {
stopTimeCount()
}
})
// 次数
let count = 0
watch(
webMsgSend,
function (newValue, oldValue) {
console.log('🚀 ~ newValue:', newValue)
if (checkStore.selectTestItems.preTest == false && newValue.requestId != 'formal_real') {
if (testLogList[0].log == '正在检测,请稍等...' || testLogList[0].log == '暂无数据,等待检测开始') {
testLogList.shift()
setLogList('info', '初始化开始!')
}
let str =
newValue.requestId == 'yjc_sbtxjy'
? '设备通讯校验'
: newValue.requestId == 'yjc_mxyzxjy'
? '模型一致性检验'
: newValue.requestId == 'yjc_align'
? '实时数据对齐检验'
: newValue.requestId == 'YJC_xujy'
? '相序校验'
: ''
// 预检测处理
switch (newValue.code) {
case 25001:
// 成功
if (newValue.data != undefined) return
setLogList('info', str + '成功!')
if (newValue.requestId == 'YJC_xujy') setLogList('info', '初始化成功!')
break
case 25003:
// 失败
if (newValue.data != undefined) return
setLogList('error', str + '失败!')
emit('update:testStatus', 'error')
stopTimeCount()
if (newValue.requestId == 'YJC_xujy') setLogList('info', '初始化失败!')
break
}
}
// 预监测 正式检测
else if (newValue.requestId == 'formal_real') {
if (testLogList[0].log == '正在检测,请稍等...' || testLogList[0].log == '暂无数据,等待检测开始') {
testLogList.shift()
}
switch (newValue.code) {
case 25001:
let result: CheckData.ScriptChnItem[] = []
scriptData.forEach(item => {
// 处理当前节点的数据
let temp: CheckData.ScriptChnItem = {
scriptType: item.id,
scriptName: item.scriptName,
devices: []
}
let message = JSON.parse(newValue.data)
for (let i = 0; i < message?.length; i++) {
temp.devices.push({
deviceId: message[i].deviceId,
deviceName: message[i].deviceName,
chnResult: message[i].chnResult
})
}
result.push(temp)
})
Object.assign(checkResult, result)
setLogList('info', '检测完成!')
stopTimeCount()
updatePercentage()
break
case 25003:
setLogList('error', '检测失败!')
stopTimeCount()
updatePercentage()
break
default:
if (newValue.code != 10201) {
setLogList(newValue.code == 10200 ? 'info' : 'error', newValue.data)
}
break
}
}
switch (newValue.requestId) {
case 'connect':
switch (newValue.operateCode) {
case 'Contrast_Dev':
setLogList('error', '设备服务端连接失败!')
stopTimeCount()
break
}
break
case 'unknown_operate':
break
case 'error_flow_end':
ElMessageBox.alert(`当前流程存在异常结束!`, '检测失败', {
confirmButtonText: '确定',
type: 'error'
})
setLogList('error', '当前流程存在异常结束!')
stopTimeCount()
break
case 'socket_timeout':
ElMessageBox.alert(`设备连接异常,请检查设备连接情况!`, '检测失败', {
confirmButtonText: '确定',
type: 'error'
})
setLogList('error', '设备连接异常,请检查设备连接情况!')
stopTimeCount()
break
case 'server_error':
ElMessageBox.alert('服务端主动关闭连接!', '初始化失败', {
confirmButtonText: '确定',
type: 'error'
})
setLogList('error', '服务端主动关闭连接!')
stopTimeCount()
break
case 'device_error':
ElMessageBox.alert('设备主动关闭连接!', '初始化失败', {
confirmButtonText: '确定',
type: 'error'
})
setLogList('error', '设备主动关闭连接!')
stopTimeCount()
break
}
},
{ deep: true }
)
const setLogList = (state: 'error' | 'info' | 'warning', text: string) => {
testLogList.push({
type: state,
log: `${new Date().toLocaleString()}` + text
})
}
// 更新进度条
const updatePercentage = async () => {
if (testLogList.length - 1 < checkStore.chnNumList.length) {
percentage.value = Math.trunc(((testLogList.length - 1) / checkStore.chnNumList.length) * 100)
} else {
percentage.value = 100
emit('update:testStatus', 'success')
let { data: autoGenerate } = await getAutoGenerate()
if (autoGenerate == 1) {
//调用自动生成报告接口
let devIdList = checkStore.devices.map(item => {
return item.deviceId
})
// await generateDevReport({
// planId: checkStore.plan.id,
// devIdList: devIdList,
// scriptId: checkStore.plan.scriptId,
// planCode: checkStore.plan.code + ''
// })
}
stopTimeCount()
ElMessageBox.alert(
'检测全部结束,你可以停留在此页面查看检测结果,或返回首页进行复检、报告生成和归档等操作',
'检测完成',
{
confirmButtonText: '确定'
}
)
// 关闭WebSocket连接
emit('closeWebSocket')
//clear();
}
}
// ========== 时间计数器管理函数 ==========
// 开始计时
const startTimeCount = () => {
if (!timer) {
timer = setInterval(() => {
timeCount.value = timeCount.value + 1
timeView.value = secondToTime(timeCount.value)
}, 1000)
}
}
// 停止计时
const stopTimeCount = () => {
if (timer) {
clearInterval(timer)
timer = null
}
}
// 将秒数转换为 HH:MM:SS 格式
const secondToTime = (second: number) => {
let h: string | number = Math.floor(second / 3600) // 小时
let m: string | number = Math.floor((second - h * 3600) / 60) // 分钟
let s: string | number = Math.floor(second % 60) // 秒
// 补齐前导零
h = h < 10 ? '0' + h : h
m = m < 10 ? '0' + m : m
s = s < 10 ? '0' + s : s
return h + ':' + m + ':' + s
}
onBeforeMount(async () => {})
const showTestLog = () => {
drawer.value = true
}
// 初始化检测脚本数据
const initScriptData = async () => {
scriptData = []
const pattern = dictStore.getDictData('Pattern').find(item => item.name === modeStore.currentMode)?.id ?? ''
let response: any = await getBigTestItem({
reCheckType: checkStore.reCheckType,
planId: checkStore.plan.id,
devIds: checkStore.devices.map(item => item.deviceId),
patternId: pattern
})
// 格式化脚本数据
let temp = response.data.map((item: any) => {
return {
...item,
scriptName: item.scriptName
}
})
// 保存脚本数据并设置总数
scriptData.push(...temp)
checkTotal = scriptData.length
}
// 初始化设备列表
const initDeviceList = () => {
Object.assign(deviceList, checkStore.devices)
}
// 初始化检测结果 (详细到通道)
// 修改函数定义
const initCheckResult = (defaultValue: CheckData.ChnCheckResultEnum) => {
let result: CheckData.ScriptChnItem[] = []
scriptData.forEach(item => {
let temp: CheckData.ScriptChnItem = {
scriptType: item.id,
scriptName: item.scriptName,
devices: []
}
for (let i = 0; i < deviceList?.length; i++) {
let tempChnResult: CheckData.ChnCheckResultEnum[] = []
for (let j = 0; j < checkStore.chnNumList.length; j++) {
tempChnResult.push(defaultValue)
}
temp.devices.push({
deviceId: deviceList[i].deviceId,
deviceName: deviceList[i].deviceName,
chnResult: tempChnResult
})
}
result.push(temp)
})
Object.assign(checkResult, result)
}
const scrollToBottom = () => {
if (scrollContainerRef.value) {
scrollContainerRef.value.scrollTop = scrollContainerRef.value.scrollHeight + 70
}
}
watch(
testLogList,
() => {
scrollToBottom()
},
{ deep: true }
)
// 点击查看设备通道检测详情。参数1设备信息参数2通道号-1代表查看全部通道
const handleClick = (item: any, chnNum: number, scriptType: string) => {
let checkResultItem = checkResult.find(obj => obj.scriptType === scriptType)
let flag = -1
if (checkResultItem) {
let device = checkResultItem.devices.find(obj => obj.deviceId === item.deviceId)
if (device) {
let chnResult = device.chnResult
if (chnNum === -1) {
if (chnResult.findIndex(obj => obj === CheckData.ChnCheckResultEnum.TIMEOUT) !== -1) {
flag = 0
}
if (chnResult.findIndex(obj => obj === CheckData.ChnCheckResultEnum.ERRORDATA) !== -1) {
flag = 1
}
} else {
if (chnResult[chnNum - 1] === CheckData.ChnCheckResultEnum.TIMEOUT) {
flag = 0
}
if (chnResult[chnNum - 1] === CheckData.ChnCheckResultEnum.ERRORDATA) {
flag = 1
}
}
}
}
if (flag === 0) {
ElMessageBox.alert('连接超时,请检查设备通讯是否正常', '连接超时', {
confirmButtonText: '确定',
type: 'warning'
})
}
if (flag === -1 || flag === 1) {
checkStore.setShowDetailType(2)
dataCheckSingleChannelSingleTestPopupRef.value?.open(item, chnNum + '', item.devices[0].deviceId, 1)
}
}
const handlePause = () => {
//emit('sendPause')
testLogList.push({
type: 'error',
log: `${new Date().toLocaleString()}:当前测试小项正在执行中,将在该小项执行结束后暂停...`
})
}
const initializeParameters = async () => {
await initScriptData()
initDeviceList()
initCheckResult(CheckData.ChnCheckResultEnum.UNKNOWN)
percentage.value = 0
timeCount.value = 0
timeView.value = '00:00:00'
testLogList.splice(0, testLogList.length, {
type: 'info',
log: checkStore.selectTestItems.preTest ? '正在检测,请稍等...' : '暂无数据,等待检测开始'
})
}
//
onMounted(() => {
if (!checkStore.selectTestItems.preTest) {
// 判断是否预检测
initializeParameters()
}
})
defineExpose({
initializeParameters,
handlePause,
showTestLog,
startTimeCount
})
</script>
<style scoped lang="scss">
:deep(.el-table .header-row) {
background-color: #f5f7fa;
}
:deep(.el-table .warning-row) {
color: red;
}
.el-table .success-row {
--el-table-tr-bg-color: var(--el-color-success-light-9);
}
.dialog {
display: flex;
flex-direction: column;
overflow-y: hidden;
overflow-x: hidden;
}
.dialog-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-right: 10px;
margin-bottom: 10px;
.timeView {
display: flex;
align-items: center;
color: #91cc75;
width: 28%;
margin-right: 0px;
text-align: left;
font-size: 26px;
font-weight: bold;
}
}
.dialog-content {
max-height: 450px;
overflow-y: hidden;
}
:deep(.el-collapse-item__header) {
height: 30px;
}
.dialog-log {
height: 50px;
overflow-y: hidden;
p {
margin: 5px 0;
font-size: 14px;
}
}
.drawer-container {
:deep(header.el-drawer__header) {
color: #fff !important;
background-color: var(--el-color-primary) !important;
.el-drawer__close-btn svg:hover {
color: #ccc !important;
}
.el-drawer__title {
color: #fff !important;
}
}
}
.loading-box {
animation: loading 1.5s linear infinite;
}
@keyframes loading {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>
<style lang="scss" scoped>
:deep(.el-button--small) {
height: 20px !important;
width: 20px !important;
}
:deep(.el-table--default td) {
padding: 5px 0 !important;
}
</style>