forked from gzt/A8000
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1171 lines
30 KiB
1171 lines
30 KiB
<template>
|
|
<div id="index-container">
|
|
<div class="loading-overlay" v-if="isLoading">
|
|
<div class="loading-content">
|
|
<div class="loading-spinner"></div>
|
|
<span class="loading-text">{{ shutdownMessage }}</span>
|
|
</div>
|
|
</div>
|
|
<el-header class="nav-bar">
|
|
<div class="logo">
|
|
<img src="@/assets/Index/logo.svg" alt="" width="150px" />
|
|
</div>
|
|
<div class="nav-tabs" @click="handleTabClick">
|
|
<!-- 使用 router-link 进行路由跳转 -->
|
|
<router-link
|
|
to="/index/regular"
|
|
class="nav-tab"
|
|
:class="{ active: selectedTab === '常规' }"
|
|
data-tab="常规"
|
|
>常规</router-link
|
|
>
|
|
<router-link
|
|
to="/index/history"
|
|
class="nav-tab"
|
|
:class="{ active: selectedTab === '历史' }"
|
|
data-tab="历史"
|
|
>历史</router-link
|
|
>
|
|
<router-link
|
|
to="/index/setting"
|
|
class="nav-tab"
|
|
:class="{ active: selectedTab === '设置' }"
|
|
data-tab="设置"
|
|
>设置</router-link
|
|
>
|
|
</div>
|
|
<div
|
|
v-if="deviceStore.deviceState.workState === 'IDLE'"
|
|
class="test-control"
|
|
>
|
|
<!-- 显示开始测试按钮 -->
|
|
<el-button type="primary" class="start-test" @click="startTest"
|
|
>开始测试</el-button
|
|
>
|
|
</div>
|
|
<div v-else class="test-control">
|
|
<!-- 显示暂停和停止按钮 -->
|
|
<el-button
|
|
type="warning"
|
|
class="pause-test"
|
|
@click="pauseTest"
|
|
v-if="deviceStore.deviceState.workState !== 'PAUSE'"
|
|
>暂停</el-button
|
|
>
|
|
<el-button
|
|
type="success"
|
|
class="continue-test"
|
|
@click="continueTest"
|
|
v-else
|
|
>继续</el-button
|
|
>
|
|
<el-button type="danger" class="stop-test" @click="stopTest"
|
|
>停止</el-button
|
|
>
|
|
</div>
|
|
</el-header>
|
|
|
|
<!-- 间隔线 -->
|
|
<div class="interval">
|
|
<div
|
|
class="blue-line"
|
|
:style="{ width: `${lineWidth}px`, left: `${lineLeft}px` }"
|
|
></div>
|
|
</div>
|
|
|
|
<!-- 主内容区域 -->
|
|
<main class="main-content">
|
|
<router-view v-slot="{ Component }">
|
|
<keep-alive :exclude="['TubeUserId', 'EmergencyForm']">
|
|
<component :is="Component" />
|
|
</keep-alive>
|
|
</router-view>
|
|
</main>
|
|
|
|
<!-- 底部操作信息 -->
|
|
<el-footer class="footer-info">
|
|
|
|
<el-dropdown placement="top-start" style="margin-left:-10px">
|
|
<div class="user-card">
|
|
<img class="user-logo" src="@/assets/Index/user.svg" />
|
|
<div class="user-name">
|
|
{{
|
|
deviceStore.contextState.loginFlag
|
|
? deviceStore.contextState.loginUser.account
|
|
: '未登录'
|
|
}}
|
|
</div>
|
|
</div>
|
|
<template #dropdown>
|
|
<el-dropdown-menu style="width:100px">
|
|
<el-dropdown-item >
|
|
<div @click="onInitDevice" style="display: flex;">
|
|
<img :src="InitSvg" alt="Icon" width="15" />
|
|
<div class="pd-5">初始化</div>
|
|
</div>
|
|
</el-dropdown-item>
|
|
<el-dropdown-item >
|
|
<div @click="onCloseDevice" style="display: flex;">
|
|
<img :src="CloseSvg" alt="Icon" width="15" />
|
|
<div class="pd-5">关机</div>
|
|
</div>
|
|
</el-dropdown-item>
|
|
<el-dropdown-item>
|
|
<div @click="onLogout" style="display: flex;">
|
|
<img :src="LogoutSvg" alt="Icon" width="15" />
|
|
<div class="pd-5">退出</div>
|
|
</div>
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
|
|
<div class="equipment-status" @click="showRecentMsgDialog = true">
|
|
<div
|
|
class="status-text"
|
|
:class="deviceStore.messageState.topMessage.messageLevel"
|
|
>
|
|
{{ deviceStore.messageState.topMessage.message }}
|
|
</div>
|
|
</div>
|
|
<div class="time-card">
|
|
<img class="time-logo" src="@/assets/Index/clock.svg" />
|
|
<div class="time-text">
|
|
<Time></Time>
|
|
</div>
|
|
</div>
|
|
</el-footer>
|
|
<el-dialog
|
|
class="recent-msg-dialog"
|
|
v-model="showRecentMsgDialog"
|
|
title="最近消息"
|
|
width="700"
|
|
>
|
|
<div class="msg-container">
|
|
<div
|
|
class="msg-item"
|
|
v-for="item in deviceStore.messageState.messageBoxList"
|
|
:key="item.time"
|
|
:class="[item.messageLevel]"
|
|
>
|
|
<span>{{ formatDate(item.time) }}</span>
|
|
<span>{{ item.message }}</span>
|
|
</div>
|
|
</div>
|
|
</el-dialog>
|
|
|
|
<el-dialog
|
|
class="event-report-dialog"
|
|
v-model="showEventReportDlg"
|
|
width="600"
|
|
:show-close="false"
|
|
>
|
|
<template #header>
|
|
<div class="report-title">
|
|
{{ currEventReport ? currEventReport.prompt.title : '' }}
|
|
</div>
|
|
<div class="report-desc">
|
|
{{ currEventReport ? currEventReport.prompt.info : '' }}
|
|
</div>
|
|
</template>
|
|
<div class="report-detail">
|
|
<div
|
|
v-if="
|
|
currEventReport && currEventReport.prompt.detailInfoType === 'Forms'
|
|
"
|
|
>
|
|
<div
|
|
class="report-form-item"
|
|
v-for="(item, index) in currEventReport.prompt.detailInfo"
|
|
:key="index"
|
|
>
|
|
<span>{{ item.name + ' :' }}</span>
|
|
<span>{{ item.description }}</span>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-else-if="
|
|
currEventReport && currEventReport.prompt.detailInfoType === 'Table'
|
|
"
|
|
>
|
|
<div class="report-table-container">
|
|
<div class="report-table-header">
|
|
<div
|
|
v-for="(item, index) in currEventReport.prompt.detailInfo
|
|
.header"
|
|
:key="index"
|
|
>
|
|
<span>{{ item }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="report-table-rows">
|
|
<div
|
|
class="report-table-row"
|
|
v-for="(row, index) in currEventReport.prompt.detailInfo.vars"
|
|
:key="index"
|
|
>
|
|
<div v-for="(col, idx) in row" :key="idx">
|
|
{{ col.val }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<span v-else>
|
|
{{ currEventReport ? currEventReport.prompt.detailInfo : '' }}
|
|
</span>
|
|
</div>
|
|
<template #footer>
|
|
<div class="dialog-footer">
|
|
<el-button type="primary" @click="onConfirmReport"> 确定 </el-button>
|
|
</div>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<InitWarn
|
|
v-if="showModal"
|
|
:visible="showModal"
|
|
title="确认操作"
|
|
message="确认试管架中的试管槽是否取下,避免自检/自动复位时对设备产生损坏!"
|
|
icon="/src/assets/Warn.svg"
|
|
cancelText="返回"
|
|
confirmText="确认取下/开始复位"
|
|
@close="showModal = false"
|
|
@confirm="handleConfirm"
|
|
/>
|
|
<!-- 通用加载弹窗组件 -->
|
|
<LoadingModal
|
|
v-if="showDeviceResettingModal"
|
|
:visible="showDeviceResettingModal"
|
|
title="正在自检/自动复位中"
|
|
message="请不要有任何手动操作!"
|
|
cancelText="返回"
|
|
confirmText="确认取下/开始复位"
|
|
disableButtons
|
|
@close="showDeviceResettingModal = false"
|
|
/>
|
|
<LoadingModal
|
|
v-if="showDeviceWaitingModal"
|
|
:visible="showDeviceWaitingModal"
|
|
:title="deviceWaitingModelInfo.title"
|
|
:message="deviceWaitingModelInfo.message"
|
|
:showBtns="false"
|
|
/>
|
|
<!-- 自动自检已完成 -->
|
|
<InitWarn
|
|
v-if="showAlreadyModal"
|
|
:visible="showAlreadyModal"
|
|
title="确认操作"
|
|
message="自检/自动复位已完成!"
|
|
icon="/src/assets/OK.svg"
|
|
cancelText="返回"
|
|
confirmText="确认"
|
|
@close="showAlreadyModal = false"
|
|
@confirm="handleAlreadyConfirm"
|
|
/>
|
|
<!-- 自动自检失败 -->
|
|
<InitWarn
|
|
v-if="showFailModal"
|
|
:visible="showFailModal"
|
|
:title="failMessage.title"
|
|
:message="failMessage.message"
|
|
icon="/src/assets/Warn.svg"
|
|
cancelText="返回"
|
|
confirmText="重试"
|
|
@close="showFailModal = false"
|
|
@confirm="confirmFail"
|
|
/>
|
|
<InitWarn
|
|
v-if="idCardInserted"
|
|
:visible="idCardInserted"
|
|
title="检测到id卡插入,是否保存?"
|
|
:message="idCardMessage"
|
|
cancelText="返回"
|
|
icon="/src/assets/update-pin-icon.svg"
|
|
confirmText="确认保存"
|
|
@close="idCardInserted = false"
|
|
@confirm="saveIdInfo"
|
|
/>
|
|
<InitWarn
|
|
v-if="showErrorModal"
|
|
:visible="showErrorModal"
|
|
title="错误提示"
|
|
:message="ErrorMessage"
|
|
icon="/src/assets/Warn.svg"
|
|
cancelText="返回"
|
|
confirmText="确认"
|
|
@close="confirmError"
|
|
@confirm="confirmError"
|
|
/>
|
|
<InitWarn
|
|
v-if="showWarnModal"
|
|
:visible="showWarnModal"
|
|
title="注意"
|
|
:message="WarnMessage"
|
|
cancelText="返回"
|
|
icon="/src/assets/update-pin-icon.svg"
|
|
confirmText="确认"
|
|
@close="confirmWarn"
|
|
@confirm="confirmWarn"
|
|
/>
|
|
<Confirm :isLoading="confirmVisible" :confirmInfo="confirmInfo"></Confirm>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { useRouter } from 'vue-router'
|
|
import { ref, onMounted, computed, watch } from 'vue'
|
|
import { ElDialog } from 'element-plus'
|
|
import { Time, InitWarn, LoadingModal } from './components/Consumables'
|
|
import {
|
|
startWork,
|
|
pauseWork,
|
|
continueWork,
|
|
stopWork,
|
|
getDeviceWorkState,
|
|
getInitState,
|
|
initDevice,
|
|
saveMountedCardInfo,
|
|
closeBuzzer,
|
|
getProjectInfo,
|
|
getBloodTypes,
|
|
confirmPromptInfo,
|
|
} from '../../services/index'
|
|
|
|
import {
|
|
useConsumablesStore,
|
|
useDeviceStore,
|
|
useEmergencyStore,
|
|
useSettingTestTubeStore,
|
|
useTestTubeStore,
|
|
} from '../../store'
|
|
import { createWebSocket } from '../../websocket/socket'
|
|
import type {
|
|
AppEventMessage,
|
|
ConsumablesStateMessage,
|
|
DeviceContextStateMessage,
|
|
DeviceWorkStateMessage,
|
|
EmergencyPosStateMessage,
|
|
EventReport,
|
|
FooterMessageState,
|
|
IncubationPlateStateMessage,
|
|
OptScanModuleStateMessage,
|
|
SensorStateMessage,
|
|
TubeHolderSettingMessage,
|
|
TubeHolderStateMessage,
|
|
} from '../../websocket/socket'
|
|
import { getServerInfo } from '../../utils/getServerInfo'
|
|
import { eventBus } from '../../eventBus'
|
|
import { logout, shutdown } from '@/services/Login/login'
|
|
import { useRunningStore } from '@/store/modules/running'
|
|
import { formatDate } from '@/utils/formDate'
|
|
import { eMessage } from './utils'
|
|
|
|
import InitSvg from '@/assets/init.svg'
|
|
import CloseSvg from '@/assets/close.svg'
|
|
import LogoutSvg from '@/assets/logout.svg'
|
|
|
|
|
|
const selectedTab = ref(sessionStorage.getItem('selectedTab') || '常规')
|
|
const lineWidth = ref(0)
|
|
const lineLeft = ref(0)
|
|
const showModal = ref(false)
|
|
const showDeviceResettingModal = ref(false)
|
|
const showDeviceWaitingModal = ref(false)
|
|
const deviceWaitingModelInfo = ref({
|
|
title: '设备正在响应中',
|
|
message: '请不要有任何手动操作!',
|
|
})
|
|
const showAlreadyModal = ref(false)
|
|
const showFailModal = ref(false)
|
|
// const checkData = ref<CheckItem[]>([]);
|
|
// const failItems = ref<CheckItem[]>([]);
|
|
const consumableStore = useConsumablesStore()
|
|
const deviceStore = useDeviceStore()
|
|
const runningStore = useRunningStore()
|
|
const settingTubeStore = useSettingTestTubeStore()
|
|
const tubeRackStore = useTestTubeStore()
|
|
const emergencyStore = useEmergencyStore()
|
|
//关机中message
|
|
const shutdownMessage = ref('正在关机中…')
|
|
const confirmInfo = ref({})
|
|
const confirmVisible = ref(false)
|
|
const idCardMessage = ref('是否保存id卡信息')
|
|
|
|
const failMessage = ref({
|
|
title: '检测失败',
|
|
message: '',
|
|
}) // 存储动态生成的错误信息
|
|
|
|
const idCardInserted = ref(false) // id卡插入状态
|
|
//事件状态
|
|
const EventText = ref<string>('闲置...')
|
|
const showWarnModal = ref(false)
|
|
const ErrorMessage = ref<string>('')
|
|
const showErrorModal = ref(false)
|
|
const WarnMessage = ref<string>('')
|
|
|
|
const showRecentMsgDialog = ref(false)
|
|
|
|
// WebSocket 实例
|
|
const eventUrl = getServerInfo('/api/v1/app/ws/event')
|
|
const wsEvent = createWebSocket(eventUrl.wsUrl)
|
|
const stateUrl = getServerInfo('/api/v1/app/ws/state')
|
|
const wsState = createWebSocket(stateUrl.wsUrl)
|
|
|
|
const eventReports = ref<EventReport[]>([])
|
|
const showEventReportDlg = ref(false)
|
|
const currEventReport = computed(() => {
|
|
return eventReports.value.length > 0 ? eventReports.value[0] : undefined
|
|
})
|
|
const onConfirmReport = async () => {
|
|
if (eventReports.value.length > 0) {
|
|
const report = eventReports.value.shift()
|
|
const res = await confirmPromptInfo(report!.eventId)
|
|
if (res && res.success) {
|
|
// do nothing
|
|
} else {
|
|
res && res.data && res.data.info && console.error(res.data.info)
|
|
}
|
|
}
|
|
if (eventReports.value.length === 0) {
|
|
showEventReportDlg.value = false
|
|
}
|
|
}
|
|
|
|
const handleSocketClose = (num: number) => {
|
|
if (num === 0) {
|
|
// socket连接断开
|
|
deviceWaitingModelInfo.value = {
|
|
title: '系统产生错误,请重启系统',
|
|
message: '请尽快重启系统',
|
|
}
|
|
showDeviceWaitingModal.value = true
|
|
} else if (num === -1) {
|
|
// 从断开 到 恢复连接, 关闭弹框
|
|
showDeviceWaitingModal.value = false
|
|
}
|
|
// else if (num === 5) {
|
|
// // 重试达到最大次数,重试,取消。
|
|
// failMessage.value = {
|
|
// title: '恢复连接失败',
|
|
// message: '未能恢复连接,建议重启设备',
|
|
// }
|
|
// showFailModal.value = true
|
|
// }
|
|
}
|
|
|
|
// 处理应用事件消息
|
|
// const formatDate = (date: any) => new Date(date).toLocaleDateString()
|
|
const handleAppEvent = (data: AppEventMessage['data']) => {
|
|
console.log('🚀 ~ handleAppEvent ~ data:', data)
|
|
if (data.typeName === 'AppPromptEvent') {
|
|
eventReports.value.push(data)
|
|
showEventReportDlg.value = true
|
|
} else if (data.typeName === 'AppIDCardMountEvent') {
|
|
consumableStore.setIdCardInserted(true)
|
|
let projectInfo = data.projectInfo
|
|
consumableStore.setIdCardInfo(projectInfo)
|
|
//显示部分id卡信息。 项目名称,过期时间,批次号
|
|
idCardInserted.value = true
|
|
//接收端是用v-html处理
|
|
idCardMessage.value = `
|
|
<div style='line-height:0.5;margin-top: -90px;'>
|
|
<div style='margin-left:25%'>
|
|
<div style='display:flex'><div>项目名称:</div><div>${projectInfo.projName}</div></div>
|
|
<div style='display:flex'><div>批次号:</div><div>${projectInfo.lotId}</div></div>
|
|
<div style='display:flex'><div>过期时间:</div><div>${formatDate(projectInfo.expiryDate)}</div></div>
|
|
</div>
|
|
</div>`
|
|
} else if (data.typeName === 'AppIDCardUnmountEvent') {
|
|
consumableStore.setIdCardInserted(false)
|
|
consumableStore.setIdCardInfo(null)
|
|
idCardInserted.value = false
|
|
}
|
|
}
|
|
|
|
//注销用户
|
|
const router = useRouter()
|
|
const onLogout = () => {
|
|
logout().then(() => {
|
|
router.push({
|
|
path: '/login',
|
|
})
|
|
sessionStorage.setItem('token', '')
|
|
})
|
|
}
|
|
|
|
const confirmFail = async () => {
|
|
showFailModal.value = false
|
|
await startInit()
|
|
}
|
|
//确认错误事件
|
|
const confirmError = async () => {
|
|
showErrorModal.value = false
|
|
//关闭蜂鸣器
|
|
await closeBuzzer()
|
|
EventText.value = '闲置...'
|
|
}
|
|
//确认警告事件
|
|
const confirmWarn = async () => {
|
|
showWarnModal.value = false
|
|
}
|
|
//保存id卡信息
|
|
const saveIdInfo = async () => {
|
|
const res = await saveMountedCardInfo()
|
|
if (res.success) {
|
|
console.log('保存id卡信息成功')
|
|
idCardInserted.value = false
|
|
}
|
|
}
|
|
|
|
//初始化设备
|
|
const onInitDevice = ()=> {
|
|
eventBus.emit('initDevice')
|
|
}
|
|
|
|
//关机
|
|
let isLoading = ref(false)
|
|
const onCloseDevice = ()=> {
|
|
// ElMessageBox.alert()
|
|
//检查设备状态 设备运行时不可关机
|
|
const deviceState = deviceStore.deviceState.workState;
|
|
if(deviceState == 'WORKING'){
|
|
showWarnModal.value = true;
|
|
WarnMessage.value = '设备正在运行中,不能进行关机操作'
|
|
return
|
|
}
|
|
|
|
confirmVisible.value = true
|
|
confirmInfo.value = {
|
|
title: '提示',
|
|
message: '请确认是否进行关机操作?',
|
|
cancelBtn: '取消',
|
|
OkBtn: '确认',
|
|
confirmCallback:()=>{
|
|
//关机时的遮罩层
|
|
isLoading.value = true;
|
|
confirmVisible.value = false;
|
|
//有时接口返回的太快,看不到loading状态,延迟1s,看一到loading状态。
|
|
setTimeout(async ()=>{
|
|
await shutdown()
|
|
shutdownMessage.value = '设备已关机,请拔掉电源'
|
|
},1000)
|
|
},
|
|
cancelCallback: ()=>{
|
|
confirmVisible.value = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
const showInitDeviceAlert = () => {
|
|
showModal.value = true
|
|
}
|
|
|
|
const handleDeviceState = (data: DeviceWorkStateMessage['data']) => {
|
|
deviceStore.setDeviceState(data)
|
|
}
|
|
const handleDeviceContextState = (data: DeviceContextStateMessage['data']) => {
|
|
deviceStore.setContextState(data)
|
|
}
|
|
const handleSensorState = (data: SensorStateMessage['data']) => {
|
|
deviceStore.setSensorState(data)
|
|
}
|
|
const handleFooterState = (data: FooterMessageState['data']) => {
|
|
deviceStore.setMessageState(data)
|
|
}
|
|
const handleTubeHolderStateMessage = (data: TubeHolderStateMessage['data']) => {
|
|
runningStore.setTubeHolderState(data)
|
|
}
|
|
const handleTubeHolderSettingMessage = (
|
|
data: TubeHolderSettingMessage['data'],
|
|
) => {
|
|
tubeRackStore.setTubeRacks(data)
|
|
}
|
|
|
|
const handleOptScanModuleStateMessage = (
|
|
data: OptScanModuleStateMessage['data'],
|
|
) => {
|
|
runningStore.setOptScanModuleState(data)
|
|
}
|
|
|
|
const handleIncubationPlateStateMessage = (
|
|
data: IncubationPlateStateMessage['data'],
|
|
) => {
|
|
runningStore.setSubTanks(data.subtanks)
|
|
}
|
|
|
|
const handleConsumablesState = (data: ConsumablesStateMessage['data']) => {
|
|
consumableStore.setConsumablesData(data)
|
|
}
|
|
|
|
const handleEmergencyPosState = (data: EmergencyPosStateMessage['data']) => {
|
|
emergencyStore.setInfo(data.tube)
|
|
}
|
|
|
|
const getProjectList = async () => {
|
|
const res = await getProjectInfo()
|
|
if (res.success) {
|
|
settingTubeStore.setSupportedProjects(res.data)
|
|
}
|
|
}
|
|
const getBloodTypeList = async () => {
|
|
const res = await getBloodTypes()
|
|
if (res.success) {
|
|
settingTubeStore.setBloodTypes(res.data)
|
|
}
|
|
}
|
|
onMounted(() => {
|
|
eventBus.on('initDevice', showInitDeviceAlert)
|
|
eventBus.on('socketClosed', handleSocketClose)
|
|
|
|
wsEvent.subscribe<AppEventMessage>('AppEvent', handleAppEvent)
|
|
wsEvent.connect()
|
|
wsState.subscribe<ConsumablesStateMessage>(
|
|
'ConsumablesState',
|
|
handleConsumablesState,
|
|
)
|
|
wsState.subscribe<DeviceWorkStateMessage>(
|
|
'DeviceWorkState',
|
|
handleDeviceState,
|
|
)
|
|
wsState.subscribe<DeviceContextStateMessage>(
|
|
'DeviceContext',
|
|
handleDeviceContextState,
|
|
)
|
|
wsState.subscribe<SensorStateMessage>('SensorState', handleSensorState)
|
|
wsState.subscribe<FooterMessageState>('MessageBoxState', handleFooterState)
|
|
wsState.subscribe<TubeHolderStateMessage>(
|
|
'TubeHolderState',
|
|
handleTubeHolderStateMessage,
|
|
)
|
|
wsState.subscribe<TubeHolderSettingMessage>(
|
|
'TubeHolderSetting',
|
|
handleTubeHolderSettingMessage,
|
|
)
|
|
wsState.subscribe<IncubationPlateStateMessage>(
|
|
'IncubationPlateState',
|
|
handleIncubationPlateStateMessage,
|
|
)
|
|
wsState.subscribe<OptScanModuleStateMessage>(
|
|
'OptScanModuleState',
|
|
handleOptScanModuleStateMessage,
|
|
)
|
|
wsState.subscribe<EmergencyPosStateMessage>(
|
|
'EmergencyPosState',
|
|
handleEmergencyPosState,
|
|
)
|
|
wsState.connect()
|
|
|
|
getProjectList()
|
|
getBloodTypeList()
|
|
})
|
|
|
|
// onBeforeUnmount(() => {
|
|
// eventBus.off('initDevice', showInitDeviceAlert)
|
|
// ws.unsubscribe<AppEventMessage>('AppEvent', handleAppEvent)
|
|
// ws.disconnect()
|
|
// })
|
|
// 动态生成错误信息
|
|
// const generateErrorMessages = (data: CheckItem[]): string[] => {
|
|
// return data
|
|
// .filter(item => !item.pass)
|
|
// .map(item => `错误:${item.typechinfo} 检测未通过,请检查设备状态。`);
|
|
// };
|
|
|
|
const untilDeviceReady = async () => {
|
|
deviceWaitingModelInfo.value = {
|
|
title: '设备正在响应中',
|
|
message: '请不要有任何手动操作!',
|
|
}
|
|
showDeviceWaitingModal.value = true
|
|
const res = await getDeviceWorkState()
|
|
if (res.ecode === 'SUC') {
|
|
if (res.data.pending) {
|
|
setTimeout(async () => await untilDeviceReady(), 250)
|
|
} else {
|
|
showDeviceWaitingModal.value = false
|
|
}
|
|
} else {
|
|
showDeviceWaitingModal.value = false
|
|
}
|
|
}
|
|
|
|
// 开始测试
|
|
const startTest = async () => {
|
|
const res = await getInitState()
|
|
if (res.ecode === 'SUC' && !res.data.deviceInited) {
|
|
eventBus.emit('initDevice')
|
|
return
|
|
}
|
|
try {
|
|
deviceWaitingModelInfo.value = {
|
|
title: '设备正在响应中',
|
|
message: '请不要有任何手动操作!',
|
|
}
|
|
showDeviceWaitingModal.value = true
|
|
const res = await startWork()
|
|
showDeviceWaitingModal.value = false
|
|
if (res && res.success) {
|
|
await untilDeviceReady()
|
|
} else {
|
|
res && res.data && res.data.info && eMessage.error(res.data.info)
|
|
}
|
|
} catch (error) {
|
|
console.error('开始测试失败:', error)
|
|
}
|
|
}
|
|
|
|
// 暂停测试
|
|
const pauseTest = async () => {
|
|
deviceWaitingModelInfo.value = {
|
|
title: '设备正在响应中',
|
|
message: '请不要有任何手动操作!',
|
|
}
|
|
showDeviceWaitingModal.value = true
|
|
const res = await pauseWork()
|
|
showDeviceWaitingModal.value = false
|
|
if (res && res.success) {
|
|
await untilDeviceReady()
|
|
} else {
|
|
res && res.data && res.data.info && eMessage.error(res.data.info)
|
|
}
|
|
}
|
|
|
|
// 停止测试时清除标记
|
|
const stopTest = async () => {
|
|
deviceWaitingModelInfo.value = {
|
|
title: '设备正在响应中',
|
|
message: '请不要有任何手动操作!',
|
|
}
|
|
showDeviceWaitingModal.value = true
|
|
const res = await stopWork()
|
|
showDeviceWaitingModal.value = false
|
|
if (res && res.success) {
|
|
await untilDeviceReady()
|
|
} else {
|
|
res && res.data && res.data.info && eMessage.error(res.data.info)
|
|
}
|
|
}
|
|
//继续测试
|
|
const continueTest = async () => {
|
|
deviceWaitingModelInfo.value = {
|
|
title: '设备正在响应中',
|
|
message: '请不要有任何手动操作!',
|
|
}
|
|
showDeviceWaitingModal.value = true
|
|
const res = await continueWork()
|
|
showDeviceWaitingModal.value = false
|
|
if (res && res.success) {
|
|
await untilDeviceReady()
|
|
} else {
|
|
res && res.data && res.data.info && eMessage.error(res.data.info)
|
|
}
|
|
}
|
|
const handleConfirm = async () => {
|
|
showModal.value = false // 关闭初始弹窗
|
|
await startInit()
|
|
}
|
|
|
|
const startInit = async () => {
|
|
await initDevice()
|
|
await pollingInitState()
|
|
}
|
|
|
|
const pollingInitState = async () => {
|
|
showDeviceResettingModal.value = true // 显示 LoadingModal
|
|
const res = await getInitState()
|
|
if (res.ecode === 'SUC') {
|
|
if (res.data.isBusy) {
|
|
setTimeout(async () => await pollingInitState(), 500)
|
|
} else {
|
|
showDeviceResettingModal.value = false
|
|
if (res.data.passed) {
|
|
console.log('初始化成功')
|
|
sessionStorage.setItem('deviceResetFinished', 'true')
|
|
showAlreadyModal.value = true
|
|
} else {
|
|
const infos = res.data.promopt.detailInfos
|
|
failMessage.value = {
|
|
title: '检测失败',
|
|
message:
|
|
infos && infos.length > 0
|
|
? infos.map((d: any) => d.name).join('\n')
|
|
: res.data.promopt.info,
|
|
}
|
|
showFailModal.value = true // 显示失败弹窗
|
|
}
|
|
}
|
|
} else {
|
|
showDeviceResettingModal.value = false
|
|
}
|
|
}
|
|
|
|
const handleAlreadyConfirm = () => {
|
|
console.log('用户确认操作')
|
|
showAlreadyModal.value = false
|
|
showFailModal.value = false
|
|
}
|
|
|
|
onMounted(() => {
|
|
checkInit()
|
|
})
|
|
|
|
// 开始检测
|
|
const checkInit = () => {
|
|
const hasExecutedReset = sessionStorage.getItem('deviceResetFinished')
|
|
if (!hasExecutedReset || hasExecutedReset === 'false') {
|
|
showModal.value = true
|
|
}
|
|
}
|
|
|
|
// 更新蓝线的宽度和左偏移量
|
|
const updateLinePosition = (element: any) => {
|
|
lineWidth.value = element.offsetWidth // 当前选中标签的宽度
|
|
lineLeft.value = element.offsetLeft // 当前选中标签的左偏移量
|
|
}
|
|
|
|
// 处理标签点击事件
|
|
const handleTabClick = (event: any) => {
|
|
const tabElement = event.target.closest('.nav-tab')
|
|
if (tabElement) {
|
|
const tab = tabElement.dataset.tab
|
|
selectedTab.value = tab
|
|
sessionStorage.setItem('selectedTab', tab) // 将选中标签存储到 localStorage
|
|
updateLinePosition(tabElement) // 更新蓝线的位置和宽度
|
|
}
|
|
}
|
|
|
|
// 页面加载时默认选中"常规"标签,并设置蓝线的位置
|
|
onMounted(() => {
|
|
const defaultTab = document.querySelector(
|
|
`.nav-tab[data-tab="${selectedTab.value}"]`,
|
|
)
|
|
if (defaultTab) {
|
|
updateLinePosition(defaultTab)
|
|
}
|
|
})
|
|
|
|
watch(
|
|
() => deviceStore.contextState,
|
|
(newVal) => {
|
|
if (!newVal.loginFlag) {
|
|
router.push({
|
|
path: '/login',
|
|
})
|
|
sessionStorage.setItem('token', '')
|
|
}
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
#index-container {
|
|
margin: 0;
|
|
padding: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background-color: #fff;
|
|
position: relative;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
.fade-slide-enter-active,
|
|
.fade-slide-leave-active {
|
|
transition:
|
|
opacity 0.5s,
|
|
transform 0.5s;
|
|
/* 控制透明度和滑动的过渡时间 */
|
|
}
|
|
|
|
.fade-slide-enter-from {
|
|
opacity: 0;
|
|
transform: translateX(20px);
|
|
/* 从右侧滑入 */
|
|
}
|
|
|
|
.fade-slide-leave-to {
|
|
opacity: 0;
|
|
transform: translateX(-20px);
|
|
/* 向左滑出 */
|
|
}
|
|
|
|
.nav-bar {
|
|
width: 100%;
|
|
height: 60px;
|
|
margin-bottom: 2px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
|
|
.logo {
|
|
width: 100px;
|
|
height: 75px;
|
|
padding-left: 10px;
|
|
}
|
|
|
|
.nav-tabs {
|
|
width: 60%;
|
|
font-size: 24px;
|
|
display: flex;
|
|
justify-content: space-around;
|
|
font-size: 40px;
|
|
|
|
.nav-tab {
|
|
cursor: pointer;
|
|
padding-bottom: 10px;
|
|
color: black;
|
|
transition: color 0.3s;
|
|
position: relative;
|
|
}
|
|
|
|
.nav-tab.active {
|
|
color: #478ffe;
|
|
}
|
|
}
|
|
|
|
.test-control {
|
|
display: flex;
|
|
justify-content: space-evenly;
|
|
padding-right: 10px;
|
|
|
|
.start-test {
|
|
width: 200px;
|
|
height: 48px;
|
|
background-color: #73bc54;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.pause-test,
|
|
.stop-test,
|
|
.continue-test {
|
|
width: 100px;
|
|
height: 48px;
|
|
font-size: 20px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.interval {
|
|
width: 100%;
|
|
height: 5px;
|
|
background-color: #f5f5f5;
|
|
position: relative;
|
|
|
|
.blue-line {
|
|
position: absolute;
|
|
bottom: 0;
|
|
height: 5px;
|
|
background-color: #478ffe;
|
|
transition: all 0.3s ease;
|
|
}
|
|
}
|
|
|
|
.footer-info {
|
|
width: 100%;
|
|
height: 50px;
|
|
display: flex;
|
|
justify-content: end;
|
|
align-items: center;
|
|
position: relative;
|
|
bottom: 0;
|
|
margin-top: auto;
|
|
|
|
> *:first-child {
|
|
margin-right: auto;
|
|
}
|
|
|
|
.user-logo,
|
|
.time-logo {
|
|
width: 36px;
|
|
height: 36px;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.user-name {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.user-card,
|
|
.time-card {
|
|
display: flex;
|
|
align-items: center;
|
|
font-size: 24px;
|
|
}
|
|
|
|
.equipment-status {
|
|
width: 440px;
|
|
height: 44px;
|
|
line-height: $height;
|
|
text-align: center;
|
|
border-radius: 10px;
|
|
margin-right: 20px;
|
|
.status-text {
|
|
border-radius: 8px;
|
|
font-size: 22px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
&.Info {
|
|
color: #333;
|
|
background-color: #f5f5f5;
|
|
}
|
|
&.Warn {
|
|
color: #fa9d3b;
|
|
background-color: rgb(239, 230, 220);
|
|
}
|
|
&.Error {
|
|
color: #d81212;
|
|
background-color: rgb(236, 216, 216);
|
|
}
|
|
}
|
|
}
|
|
.logout {
|
|
width: 70px;
|
|
text-align: center;
|
|
font-size: 20px;
|
|
margin: auto;
|
|
}
|
|
}
|
|
|
|
.main-content {
|
|
flex: 1;
|
|
min-height: 0; // 添加:防止溢出
|
|
}
|
|
}
|
|
.msg-container {
|
|
max-height: 850px;
|
|
padding: 4px;
|
|
overflow-y: auto;
|
|
.msg-item {
|
|
padding: 0 10px;
|
|
height: 45px;
|
|
line-height: $height;
|
|
font-size: 22px;
|
|
display: flex;
|
|
gap: 10px;
|
|
&:nth-child(even) {
|
|
background-color: rgb(250, 250, 250);
|
|
}
|
|
&:nth-child(odd) {
|
|
background-color: #f2f2f2;
|
|
}
|
|
&.Warn {
|
|
color: #fa9d3b;
|
|
background-color: rgb(239, 230, 220);
|
|
}
|
|
&.Error {
|
|
color: #d81212;
|
|
background-color: rgb(236, 216, 216);
|
|
}
|
|
}
|
|
}
|
|
.event-report-dialog {
|
|
font-size: 1.5rem;
|
|
.report-title {
|
|
font-size: 30px;
|
|
margin: 18px 0;
|
|
}
|
|
.report-desc {
|
|
font-size: 26px;
|
|
}
|
|
.report-detail {
|
|
font-size: 22px;
|
|
margin-bottom: 14px;
|
|
.report-form-item {
|
|
display: flex;
|
|
gap: 20px;
|
|
margin: 0 20px;
|
|
padding: 20px;
|
|
border-bottom: 1px solid lightgray;
|
|
}
|
|
.report-table-container {
|
|
}
|
|
.report-table-header {
|
|
// background-color: lightgray;
|
|
display: flex;
|
|
text-align: left;
|
|
font-size: 22px;
|
|
height: 45px;
|
|
line-height: $height;
|
|
padding: 0 20px;
|
|
> * {
|
|
flex: 1 1 33%;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
.report-table-row {
|
|
display: flex;
|
|
text-align: left;
|
|
height: 45px;
|
|
line-height: $height;
|
|
padding: 0 20px;
|
|
> * {
|
|
flex: 1 1 33%;
|
|
}
|
|
&:nth-child(even) {
|
|
background-color: rgb(250, 250, 250);
|
|
}
|
|
&:nth-child(odd) {
|
|
background-color: #f2f2f2;
|
|
}
|
|
}
|
|
}
|
|
.dialog-footer .el-button {
|
|
height: 50px;
|
|
width: 100px;
|
|
font-size: 1.5rem;
|
|
}
|
|
}
|
|
|
|
.pd-5{
|
|
padding: 0 5px;
|
|
font-size: 28px;
|
|
}
|
|
|
|
.loading-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
backdrop-filter: blur(4px);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 9998;
|
|
}
|
|
|
|
.loading-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 20px;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 60px;
|
|
height: 60px;
|
|
border: 4px solid #f3f3f3;
|
|
border-top: 4px solid #4caf50;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
.loading-text {
|
|
color: white;
|
|
font-size: 24px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.loading-progress {
|
|
width: 300px;
|
|
height: 6px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
</style>
|