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.
513 lines
16 KiB
513 lines
16 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="!isTesting" 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="!isPaused">暂停</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>
|
|
<component :is="Component" />
|
|
</keep-alive>
|
|
</router-view>
|
|
</main>
|
|
|
|
<!-- 底部操作信息 -->
|
|
<el-footer class="footer-info">
|
|
<div class="user-card">
|
|
<img class="user-logo" src="@/assets/Index/user.svg"></img>
|
|
<div class="user-name">操作人:{{ username || "未登录" }}</div>
|
|
</div>
|
|
<div class="equipment-status">
|
|
<div class="status-text">系统:{{ EventText }}</div>
|
|
</div>
|
|
<div class="time-card">
|
|
<img class="time-logo" src="@/assets/Index/clock.svg"></img>
|
|
<div class="time-text">
|
|
<Time></Time>
|
|
</div>
|
|
</div>
|
|
</el-footer>
|
|
</div>
|
|
<InitWarn v-if="showModal" :visible="showModal" title="确认操作" message="确认试管架中的试管槽是否取下,避免自检/自动复位时对设备产生损坏!"
|
|
icon="/src/assets/Warn.svg" cancelText="返回" confirmText="确认取下/开始复位" @close="showModal = false"
|
|
@confirm="handleConfirm" />
|
|
<!-- 通用加载弹窗组件 -->
|
|
<LoadingModal v-if="showLoadingModal" :visible="showLoadingModal" title="正在自检/自动复位中" message="请不要有任何手动操作!"
|
|
cancelText="返回" confirmText="确认取下/开始复位" disableButtons @close="showLoadingModal = 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" />
|
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|
import { Time, InitWarn, LoadingModal } from './Components/Consumables';
|
|
import { startWork, pauseWork, continueWork, stopWork, getInitState, initDevice, saveMountedCardInfo, openBuzzer, closeBuzzer } from '../../services/index';
|
|
import { CheckItem, User } from '../../types/Index';
|
|
import { useConsumablesStore } from '../../store';
|
|
import { createWebSocket } from '../../websocket/socket';
|
|
import type { AppEventMessage } from '../../websocket/socket';
|
|
import { getServerInfo } from '../../utils/getServerInfo';
|
|
import { eventBus } from '../../eventBus';
|
|
const selectedTab = ref(sessionStorage.getItem('selectedTab') || '常规');
|
|
const lineWidth = ref(0);
|
|
const lineLeft = ref(0);
|
|
const showModal = ref(false);
|
|
const showLoadingModal = ref(false);
|
|
const showAlreadyModal = ref(false);
|
|
const showFailModal = ref(false);
|
|
const checkData = ref<CheckItem[]>([]);
|
|
const failItems = ref<CheckItem[]>([]);
|
|
const consumableStore = useConsumablesStore();
|
|
// 新增的变量
|
|
const user = ref<User>(JSON.parse(sessionStorage.getItem('token') || '{}') as unknown as User)
|
|
const isTesting = ref(false); // 用于控制按钮的显示
|
|
const isPaused = ref(false); // 用于控制是否暂停测试
|
|
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.isIdCardInserted = true;
|
|
idCardInserted.value = true;
|
|
EventText.value = "id卡已插入"
|
|
} else if (data.typeName === 'AppIDCardUnmountEvent') {
|
|
consumableStore.isIdCardInserted = false;
|
|
idCardInserted.value = false;
|
|
EventText.value = "id卡已拔出"
|
|
} else if (data.typeName === 'AppTubeholderSettingUpdateEvent') {
|
|
EventText.value = "试管架配置更新"
|
|
} else if (data.typeName === "DoA8kStepActionEvent") {
|
|
EventText.value = data.actionStepName!
|
|
} else {
|
|
EventText.value = "闲置..."
|
|
}
|
|
};
|
|
|
|
//确认错误事件
|
|
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 startTest = async () => {
|
|
try {
|
|
const res = await startWork();
|
|
if (res.success) {
|
|
isTesting.value = true;
|
|
}
|
|
} catch (error) {
|
|
console.error('开始测试失败:', error);
|
|
isTesting.value = false;
|
|
}
|
|
const isCheck = sessionStorage.getItem('testStarted');
|
|
if (!isCheck) {
|
|
isTesting.value = false;
|
|
await startInit()
|
|
}
|
|
|
|
};
|
|
|
|
// 暂停测试
|
|
const pauseTest = async () => {
|
|
const res = await pauseWork();
|
|
if (res.success) {
|
|
isPaused.value = true;
|
|
}
|
|
// 在这里添加暂停测试的逻辑
|
|
};
|
|
|
|
// 停止测试时清除标记
|
|
const stopTest = async () => {
|
|
console.log('测试已停止');
|
|
const res = await stopWork();
|
|
if (res.success) {
|
|
isTesting.value = false;
|
|
}
|
|
};
|
|
//继续测试
|
|
const continueTest = async () => {
|
|
const res = await continueWork();
|
|
if (res.success) {
|
|
isPaused.value = false;
|
|
console.log('继续测试');
|
|
}
|
|
};
|
|
const handleConfirm = async () => {
|
|
showModal.value = false; // 关闭初始弹窗
|
|
|
|
await startInit()
|
|
};
|
|
|
|
const startInit = async () => {
|
|
await initDevice()
|
|
await pollingInitState()
|
|
}
|
|
|
|
const pollingInitState = async () => {
|
|
showLoadingModal.value = true; // 显示 LoadingModal
|
|
const res = await getInitState();
|
|
if (res.ecode === "SUC") {
|
|
if (res.data.isBusy) {
|
|
setTimeout(async () => await pollingInitState(), 500)
|
|
} else {
|
|
showLoadingModal.value = false;
|
|
if (res.data.passed) {
|
|
console.log("初始化成功")
|
|
// sessionStorage.setItem('testStarted', "true");
|
|
showAlreadyModal.value = true;
|
|
}
|
|
else {
|
|
const infos = res.data.promopt.detailInfos
|
|
failMessage.value= infos && infos.length > 0 ? infos.map(d => 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('testStarted', "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('testStarted', "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(() => {
|
|
openTest();
|
|
});
|
|
|
|
// 开始检测
|
|
const openTest = () => {
|
|
const hasExecuted = sessionStorage.getItem('testStarted');
|
|
// 如果没有标记,执行 openTest 并设置标记
|
|
if (!hasExecuted) {
|
|
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) {
|
|
console.log('🚀 ~ tabElement:', 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
.main-content {
|
|
flex: 1;
|
|
min-height: 0; // 添加:防止溢出
|
|
}
|
|
}
|
|
</style>
|