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.
 
 
 
 

696 lines
18 KiB

<template>
<div id="index-container">
<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']">
<component :is="Component" />
</keep-alive>
</router-view>
</main>
<!-- 底部操作信息 -->
<el-footer class="footer-info">
<el-dropdown placement="top-start">
<div class="user-card">
<img class="user-logo" src="@/assets/Index/user.svg" />
<div class="user-name">操作人:{{ username || '未登录' }}</div>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<img class="user-logo" src="@/assets/Index/user.svg" height="30"></img>
<button class="logout" style="width:100px" @click="onLogout">注销</button>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="equipment-status">
<div class="status-text">系统:{{ EventText }}</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>
<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="设备正在响应中"
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="检测失败"
:message="failMessage"
icon="/src/assets/Warn.svg"
cancelText="返回"
confirmText="重试"
@close="showFailModal = false"
@confirm="startInit"
/>
<InitWarn
v-if="idCardInserted"
:visible="idCardInserted"
title="检测到id卡插入"
message="是否保存id卡信息"
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"
/>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { ElMessage } from 'element-plus'
import { Time, InitWarn, LoadingModal } from './Components/Consumables'
import {
startWork,
pauseWork,
continueWork,
stopWork,
getDeviceWorkState,
getInitState,
initDevice,
saveMountedCardInfo,
openBuzzer,
closeBuzzer,
} from '../../services/index'
import { User } from '../../types/Index'
import { useConsumablesStore, useDeviceStore } from '../../store'
import { createWebSocket } from '../../websocket/socket'
import type { AppEventMessage } from '../../websocket/socket'
import { getServerInfo } from '../../utils/getServerInfo'
import { eventBus } from '../../eventBus'
import { logout } from '@/services/Login/login'
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 showAlreadyModal = ref(false)
const showFailModal = ref(false)
// const checkData = ref<CheckItem[]>([]);
// const failItems = ref<CheckItem[]>([]);
const consumableStore = useConsumablesStore()
const deviceStore = useDeviceStore()
// 新增的变量
const user = ref<User>(
JSON.parse(sessionStorage.getItem('token') || '{}') as unknown as User,
)
const username = ref<string>(user.value.account)
const failMessage = ref('') // 存储动态生成的错误信息
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>('')
// WebSocket 实例
const { wsUrl } = getServerInfo('/api/v1/app/ws/event')
const ws = createWebSocket(wsUrl)
// 处理应用事件消息
const handleAppEvent = (data: AppEventMessage['data']) => {
console.log('🚀 ~ handleAppEvent ~ data:', data)
if (data.typeName === 'AppPromptEvents' && data.prompt) {
data.prompt.forEach(async (item) => {
if (item.type === 'Error') {
showErrorModal.value = true
ErrorMessage.value = item.info
await openBuzzer()
} else if (item.type === 'Warn') {
showWarnModal.value = true
WarnMessage.value = item.info
}
})
} else if (data.typeName === 'AppIDCardMountEvent') {
consumableStore.setIdCardInserted(true)
idCardInserted.value = true
EventText.value = 'id卡已插入'
} else if (data.typeName === 'AppIDCardUnmountEvent') {
consumableStore.setIdCardInserted(false)
idCardInserted.value = false
EventText.value = 'id卡已拔出'
} else if (data.typeName === 'AppTubeholderSettingUpdateEvent') {
EventText.value = '试管架配置更新'
eventBus.emit('AppTubeSettingUpdateEvent')
} else if (data.typeName === 'DoA8kStepActionEvent') {
EventText.value = data.actionStepName!
} else {
EventText.value = '闲置...'
}
}
//注销用户
const router = useRouter()
const onLogout = () => {
logout().then(() => {
router.push({
path: '/login',
})
sessionStorage.setItem('token', '')
})
}
//确认错误事件
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 showInitDeviceAlert = () => {
showModal.value = true
}
onMounted(() => {
eventBus.on('initDevice', showInitDeviceAlert)
ws.connect()
ws.subscribe<AppEventMessage>('AppEvent', handleAppEvent)
})
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 () => {
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 {
showDeviceWaitingModal.value = true
const res = await startWork()
if (res.success) {
await untilDeviceReady()
} else {
showDeviceWaitingModal.value = false
ElMessage({
message: res.data.info,
type: 'error',
duration: 2000,
})
}
} catch (error) {
console.error('开始测试失败:', error)
}
}
// 暂停测试
const pauseTest = async () => {
showDeviceWaitingModal.value = true
const res = await pauseWork()
if (res.success) {
await untilDeviceReady()
} else {
showDeviceWaitingModal.value = false
ElMessage({
message: res.data.info,
type: 'error',
duration: 2000,
})
}
}
// 停止测试时清除标记
const stopTest = async () => {
showDeviceWaitingModal.value = true
const res = await stopWork()
if (res.success) {
await untilDeviceReady()
} else {
showDeviceWaitingModal.value = false
ElMessage({
message: res.data.info,
type: 'error',
duration: 2000,
})
}
}
//继续测试
const continueTest = async () => {
showDeviceWaitingModal.value = true
const res = await continueWork()
if (res.success) {
await untilDeviceReady()
} else {
showDeviceWaitingModal.value = false
ElMessage({
message: res.data.info,
type: 'error',
duration: 2000,
})
}
}
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 =
infos && infos.length > 0
? infos.map((d: any) => d.name).join('\n')
: res.data.promopt.info
showFailModal.value = true // 显示失败弹窗
}
}
}
}
// const checkIfResetCompleted = async () => {
// if (showFailModal.value) {
// showFailModal.value = false;
// }
// showLoadingModal.value = true; // 显示 LoadingModal
// // 模拟 2 秒延时
// await new Promise(resolve => setTimeout(resolve, 2000));
// //通过获取初始化状态来判断是否初始化成功
// const initState = await getInitState();
// if (initState.ecode === "SUC") {
// //检测初始化是否成功
// if (initState.data.passed) {
// console.log("初始化成功")
// sessionStorage.setItem('deviceResetFinished', "true");
// showLoadingModal.value = false;
// showAlreadyModal.value = true;
// } else {
// console.log("初始化失败")
// await getCheckData(); // 获取检查数据找到错误项
// const failedItems = checkData.value.filter(item => !item.pass);
// if (failedItems.length > 0) {
// const errorMessages = generateErrorMessages(failedItems);
// console.log('生成的错误信息:', errorMessages);
// failItems.value = failedItems; // 更新失败的检查项
// failMessage.value = errorMessages.join('\n'); // 更新错误信息
// showLoadingModal.value = false; // 隐藏 LoadingModal
// showFailModal.value = true; // 显示失败弹窗
// } else {
// console.log("初始化失败,但是没有失败项")
// showLoadingModal.value = false; // 隐藏 LoadingModal
// showAlreadyModal.value = true; // 显示已完成弹窗
// sessionStorage.setItem('deviceResetFinished', "true");
// }
// }
// }
// };
// const isTestTubeSlotReady = ref(false); // 试管槽状态标记
// 在获取检测数据后,判断试管槽状态
// const checkTestTubeSlotStatus = (data: CheckItem[]) => {
// const slotCheck = data.find(item => item.type === 'CHECK_TEST_TUBE_SLOT_READY');
// isTestTubeSlotReady.value = slotCheck?.pass ?? false;
// console.log('试管槽状态:', isTestTubeSlotReady.value ? '准备就绪' : '未准备好');
// };
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)
}
})
</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: 40px;
background-color: #73bc54;
font-size: 20px;
}
.pause-test,
.stop-test,
.continue-test {
width: 100px;
height: 40px;
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: 40px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
bottom: 0;
margin-top: auto;
.user-logo,
.time-logo {
width: 40px;
height: 40px;
}
.user-card,
.time-card {
display: flex;
align-items: center;
font-size: 26px;
}
.equipment-status {
background-color: #f5f5f5;
width: 400px;
height: 40px;
line-height: 40px;
text-align: center;
border-radius: 10px;
.status-text {
font-size: 26px;
}
}
.logout {
width: 70px;
text-align: center;
font-size: 20px;
margin: auto;
}
}
.main-content {
flex: 1;
min-height: 0; // 添加防止溢出
}
}
</style>