|
|
<template> <div class="graphite_home component-page overflow-auto" id="heatArea"> <div class="heat_area"> <div v-for="(item, index) in heatList" :key="item.id"> <HeatPosition :heatInfo="{ ...item, index }" :tubeIndex="index + 1" @onSelectedTray="onSelectedTray" @onSetHeatAreaTemp="onSetHeatAreaTemp" @onSelectCraft=" craftData => { onSelectCraft(item, craftData); } "></HeatPosition> </div> <!--执行中状态的遮罩层--> </div> <!--拍照区--> <div class="picture_area"> <!--加液区和拍照区可切换--> <TakePickture></TakePickture> <!-- <div v-else style="display: flex;justify-content: center;"> <div class="home_tube"> <div class="inner-circle" v-for="(tubeItem, index) in tubeList" :key="index" :style="{ background: tubeItem.color }" ></div> </div> </div> --> <!--操作区--> <div class="graphite_btn_container"> <van-button v-if="doorStatus" size="large" class="btn_size op_open_door" @click="onCloseDoor">关门</van-button> <van-button v-else size="large" class="btn_size op_open_door" @click="onOPenDoor">开门</van-button>
<van-button size="large" class="btn_size op_start_task" @click="startTask">开始实验</van-button> <van-button size="large" class="btn_size op_stop_task" @click="onEndTask">结束实验</van-button> <van-button size="large" class="btn_size op_select_craft" @click="onChooseCaft">{{ craftName }}</van-button> <van-button size="large" class="btn_size op_exec_craft" @click="onCraftStart()">{{ exeCraftName }}</van-button> <van-button size="large" class="btn_size op_add_liquid" @click="onAddLiquid">添加溶液</van-button> <van-button v-if="!isSharking" size="large" class="btn_size op_shake_up" @click="onStartShakingTube" >摇匀</van-button > <van-button v-else size="large" class="btn_size op_shake_up" @click="onStopShakingTube">结束摇匀</van-button> <van-button size="large" class="btn_size op_move_heat" @click="onMoveToHeat">移至加热</van-button> <van-button size="large" class="btn_size op_move_act" @click="onMoveToOperationArea">移至加液</van-button> <van-button size="large" class="btn_size op_move_exception" @click="onMoveToSpecial">移至特殊</van-button> <van-button v-if="!isHeating" size="large" class="btn_size op_start_heat" @click="onStartHeat" >开始加热</van-button > <van-button v-else size="large" class="btn_size op_start_heat" @click="onStopHeat">停止加热</van-button>
<van-button size="large" class="btn_size op_up_tray" @click="onUpTray">抬起托盘</van-button> </div> </div> <van-overlay :show="liquidVisible" v-if="liquidVisible" style="z-index: 9999"> <div class="liquid"> <div class="addLiquid"> <AddLiquid :currentSelectedTube="currentSelectedTube" @cancel="liquidVisible = false" @onAddSolution="onAddSolution"></AddLiquid> </div> </div> </van-overlay> <!--选择工艺--> <OverlayModal :visible="craftVisible"> <CraftList @changeVisible="changeVisible" @selectedCraft="onHandleSelectedCraft"></CraftList> </OverlayModal>
<!--实验名称--> <OverlayModal :visible="taskNameVisible"> <div class="task_name"> <div class="task_title">开始新实验</div> <div class="task_name_content"> <div class="mt-3">实验名称:</div> <div class="task_auto"> <input v-model="taskName" placeholder="实验名称" class="task_input" /> <div v-if="!taskName" style="color: red; font-size: 1rem">请输入实验名称</div> </div> </div>
<br /> <footer class="task_button"> <button class="btn-dark px-2 py-1 min-w-20" @click="onSave">保存</button> <button class="cancel_btn" @click="onCancel">取消</button> </footer> </div> </OverlayModal> </div> </template> <script lang="ts" setup> import { ref, reactive, onMounted, onUnmounted, watch } from "vue"; //@ts-ignore
import { ElMessage, ElMessageBox } from "element-plus"; import { createWebSocket, sharedWsUrl } from "@/services/socket"; import { HeatPosition, TakePickture, AddLiquid } from "./components"; import OverlayModal from "@/components/OverlayModal.vue"; import CraftList from "@/views/graphite/components/CraftList.vue"; import { injectFluid } from "@/services/task/task"; import { setOnGoingStatus, useStatusStore } from "@/stores/status"; import { getIngTask, saveTaskName, stopTask } from "@/services/task/task"; import { CmdDescMap, taskCmd, type OperationCmd } from "@/services/globalCmd/globalCmd"; import { craftStart, craftStop } from "@/services/ore/oreManage"; import { getTxnRecord } from "@/services/txn"; import { useSettingStore } from "@/stores/setting"; import { useCraftStore } from "@/stores/craft";
const craftStore = useCraftStore(); const craftInfo = ref(craftStore.craftInfo); const settingStore = useSettingStore(); const craftName = ref("选择工艺"); const exeCraftName = ref("执行工艺"); watch( () => craftStore.craftInfo, newVal => { craftInfo.value = newVal; if (newVal?.status == 1) { craftName.value = "暂停工艺"; exeCraftName.value = "停止工艺"; } } ); //设备的全局状态
const statusStore = useStatusStore(); const heatAearStatusList = ref(statusStore.status?.heatArea || []); const heatList: any = ref([]); const craftVisible = ref(false); let tubeList = reactive<any>([]); const selectedColor = "#4F85FB"; const emptyColor = "#FFFFFF"; const taskName = ref(""); let globeStatus: any = 0; onMounted(() => { //6个加热区数据
heatList.value = settingStore.heatAreaConfig.map((item: any) => { //添加一个字段,默认为未选中
item.isSelect = false; heatAearStatusList.value.forEach((areaItem: any) => { if (areaItem.hardwareId == item.hardwareId) { item = { ...item, heatAearStatus: areaItem, }; } }); return item; }); //设备16个试管的基础数据
tubeBaseConfig(); //连接socket
const wsClient = createWebSocket(sharedWsUrl); const subscription = wsClient.dataOb.subscribe(data => { if (!globeStatus) { //为了只输入一行,不想后台一直打印此处日志 TODO
console.log("globeStatus====", data); } globeStatus = 1; if (data.type === "cmd") { const cmdInfo = getTxnRecord(data.data.commandId, "task"); if (cmdInfo) { const command: any = cmdInfo.command; //@ts-ignore
const cmdName = CmdDescMap[command]; const result = data.data.success ? "执行完毕" : `执行失败 ${data.data.message}`; ElMessage({ message: `${cmdName} ${result}`, type: data.data.success ? "success" : "error", }); } } }); wsClient.connect();
onUnmounted(() => { subscription.unsubscribe(); }); });
//选中的托盘
const selectedTrayList = ref<any>([]); const selectedTrayObj: any = {}; const onSelectedTray = (heatAreaItem: any, type: string) => { heatList.value[heatAreaItem.index] = heatAreaItem; //取消选中,已经存在selectedTrayList中
let ids = selectedTrayList.value.map((tube: any) => tube.id); if (type == "isClick") { //点击加热区
if (ids.includes(heatAreaItem.id)) { heatAreaItem.isSelect = false; selectedTrayList.value = selectedTrayList.value.filter((selectedItem: any) => selectedItem.id != heatAreaItem.id); } else { heatAreaItem.isSelect = true; selectedTrayList.value.push(heatAreaItem); } } else { heatAreaItem.isSelect = true; } heatList.value.forEach((item: any) => { if (item.id == heatAreaItem.id) { item = heatAreaItem; onHeadleCraft(item); } }); console.log("hasCraftInfo---", hasCraftInfo.value); };
//选择的加热区是否选择了工艺
let hasCraftInfo = ref(false); const onHeadleCraft = (areaItem: any) => { if (areaItem.craftInfo) { hasCraftInfo.value = true; } };
//设置加热区温度
const onSetHeatAreaTemp = (dataInfo: any) => { selectedTrayObj[dataInfo.id] = dataInfo; selectedTrayList.value = Object.values(selectedTrayObj); };
//加热区选择的工艺
const onSelectCraft = (item: any, craftInfo: any) => { item.isSelect = true; item.craftInfo = craftInfo; onSelectedTray(item, "isMove"); }; //开始执行工艺
const onCraftStart = () => { if (!selectedTrayList.value.length) { ElMessage.error("请选择目标加热区"); return; }
//可能会选择多个加热区执行工艺, 批量发送指令
let hasCraft = true; let len = selectedTrayList.value.length; for (let i = 0; i < len; i++) { let item = selectedTrayList.value[i]; if (!item.craftInfo) { hasCraft = false; ElMessage.error("选择的加热区未选择工艺"); break; } const params = { craftId: null, heatId: item.id, }; if (craftInfo.value?.status == 1) { craftStop(params).then(res => { ElMessage.success("已停止执行工艺的指令"); }); return; } else { params.craftId = item.craftInfo.id; craftStart(params).then(res => { ElMessage.success("已执行工艺的指令"); }); } }
if (hasCraft) { //给选择加热区heatList加一个正在执行工艺的标识
const selectedIds = selectedTrayList.value.map((item: any) => item.id); heatList.value.forEach((item: any) => { if (selectedIds.includes(item.id)) { item.executing_craft = true; } }); } };
//停止工艺
const onCraftStop = () => {};
const tubeBaseConfig = () => { //默认为16个
for (let i = 0; i < 16; i++) { tubeList.push({ id: i + 1, color: "rgb(212, 212, 212)", }); } };
//添加溶液
const liquidVisible = ref(false); const onAddLiquid = () => { //检查加液区是否有试管。
const liquidArea = statusStore.status?.liquidArea; if (liquidArea) { liquidVisible.value = true; } else { ElMessage.error("加液区未检测到托盘,无法进行加液操作"); } };
const onAddSolution = (data: any) => { let ids = data.map((item: any) => item.id); //批量发送加液指令
const params = { injectFluids: data, }; injectFluid(params).then(res => { if (res.success) { setOnGoingStatus("injecting"); } else { ElMessage.error(res.msg); } }); globeStatus = 0; // tubeList.forEach((item: any) => {
// if (ids.includes(item.id)) {
// item.default = defaultColor;
// item.color = defaultColor;
// item.isSelected = true;
// }
// });
};
const changeVisible = () => { craftVisible.value = false; };
//选择工艺
const onChooseCaft = () => { //1、是否选择了加热区
if (!selectedTrayList.value.length) { ElMessage.error("请选择目标加热区"); return; } craftVisible.value = true; };
//选择的工艺
const onHandleSelectedCraft = (craftInfo: any) => { selectedTrayList.value.forEach((item: any) => { item.isSelect = true; item.craftInfo = craftInfo; onSelectedTray(item, "isMove"); }); const selectedIds = selectedTrayList.value.map((item: any) => item.id); heatList.value.forEach((item: any) => { if (selectedIds.includes(item.id)) { item.craftInfo = craftInfo; } }); changeVisible(); };
const onChooseTube = (tubeItem: any, index: any) => { if (!tubeItem.id) return; //@ts-ignore
let list = [...tubeList]; for (let i = 0; i < list.length; i++) { let item = list[i]; if (index == i) { item.color = selectedColor; item.isSelected = true; } else { item.color = item.default ? item.default : emptyColor; item.isSelected = false; } } tubeList = [...list]; };
//移至加热
const onMoveToHeat = () => { //1、是否选择了加热区
if (!selectedTrayList.value.length) { ElMessage.error("请选择目标加热区"); return; } //2、只能选择一个加热区
if (selectedTrayList.value.length != 1) { ElMessage.error("只能选择一个加热区"); return; } let selectedDataItem = selectedTrayList.value[0]; //2、判断选择的加热区是否已经有了试管架, 加热区是否有试管是通过设备上报的数据获取的
let hardwareId = selectedDataItem.hardwareId; // let trayStatus = heatAearStatusList.value[selectedDataItem.index].trayStatus;
let trayStatus; heatAearStatusList.value.forEach((item: any) => { if (hardwareId == item.hardwareId) { trayStatus = item.trayStatus; } }); // trayStatus: 0为无托盘,1为有托盘,2为托盘抬起
if (trayStatus == 1) { ElMessage.error("选择的加热区已有试管架,重新选择加热区"); return; }
//调用移至加热接口
const params = { heatId: selectedDataItem.id, }; const command: OperationCmd = "moveToHeatArea"; taskCmd({ command, params }).then(res => { if (res.success) { // ElMessage.success("指令已发送,请稍等");
setOnGoingStatus("movingToHeat"); } else { ElMessage.error(res.msg); } });
//指令完成成更新UI
let list = [...heatList.value]; list.forEach((item: any) => { if (item.id == selectedDataItem.id) { item.tubeList = JSON.parse(JSON.stringify(tubeList)); selectedDataItem.tubeList = JSON.parse(JSON.stringify(tubeList)); //标注该加热区是选中状态
item.isSelect = true; } }); tubeList.forEach((item: any) => { item.color = ""; item.default = ""; }); heatList.value = [...list]; };
//移至加液区(操作区)
const currentSelectedTube = ref({}); const onMoveToOperationArea = () => { //1、判断加液区是否有试管架(暂时获取不到这个状态)
//1、是否选择了试管架/加热区
if (!selectedTrayList.value.length) { ElMessage.error("请选择加热区"); return false; } //2、只能选择一个试管架/加热区
if (selectedTrayList.value.length != 1) { ElMessage.error("只能选择一个加热区"); return false; } let selectedDataItem = selectedTrayList.value[0]; currentSelectedTube.value = selectedDataItem; //3、选择的加热区有没有试管架
if (!selectedDataItem.tubeList || !selectedDataItem.tubeList.length) { ElMessage.error("选择的加热区没有试管架"); return false; } //4、发送移至加液区指令
const params = { heatId: selectedDataItem.id, }; onSendCmd("moveToActionArea", params); //更新UI
heatList.value.forEach((item: any) => { if (item.id == selectedDataItem.id) { tubeList = [...item.tubeList]; } }); onSelectedTray(selectedDataItem, "isMove"); return true; };
//移至特殊区域
const onMoveToSpecial = () => { //检查是否设置了异常区域
const systemSetting = settingStore.systemSetting; let specialArea: any = {}; if (systemSetting && systemSetting.length) { systemSetting.forEach(item => { if (item.code == "sys_setting_abnormal_area") { specialArea = item; } });
if (!specialArea.id) { ElMessage.error("未设置异常区域,请在系统配置中设置"); return; }
//是否选择了加热区的试管架
if (!selectedTrayList.value.length) { ElMessage.error("请选择试管架"); return; } //2、只能选择一个试管架/加热区
if (selectedTrayList.value.length != 1) { ElMessage.error("只能选择一个试管架"); return; } let selectedDataItem = selectedTrayList.value[0]; selectedDataItem.isSelect = false; onSelectedTray(selectedDataItem, "isMove");
const params = { heatId: selectedDataItem.id, }; onSendCmd("moveToActionArea", params); } };
//开始实验
const taskNameVisible = ref(false); const taskId = ref(); const onSave = () => { if (!taskName.value) return; const params = { name: taskName.value, }; saveTaskName(params).then(res => { if (res.success) { taskId.value = res.data.id; ElMessage.success("保存成功"); onCancel(); } else { ElMessage.error(res.msg); } }); };
const startTask = async () => { const res = await getIngTask();
if (res.data) { ElMessageBox.confirm(`上一实验"${res.data.name}"未结束,是否结束并开始新的实验`, "提示", { confirmButtonText: "确定", cancelButtonText: "取消", center: true, }) .then(() => { stopTask({ taskId: res.data.id }) .then(res => { if (res.success) { ElMessage.success("实验已停止"); taskNameVisible.value = true; } else { ElMessage.error("实验停止失败"); } }) .catch(e => { ElMessage.error(e); }); }) .catch(() => {}); } else { taskNameVisible.value = true; } };
//结束实验
const onEndTask = () => { const params = { taskId: taskId.value, }; stopTask(params) .then(res => { heatList.value.forEach((item: any) => { item.executing_craft = false; }); if (res.success) { ElMessage.success("实验已停止"); } else { ElMessage.error("实验停止失败"); } }) .catch(e => { ElMessage.error(e); }); };
const onCancel = () => { taskNameVisible.value = false; };
//开门
const doorStatus = ref(false); //开门状态 。true: 门已打开, false: 门未打开
const onOPenDoor = () => { const params = {}; const command: OperationCmd = "openDoor"; taskCmd({ command, params }).then(res => { if (res.success) { // ElMessage.success("指令已发送,请稍等");
doorStatus.value = true; setOnGoingStatus("doorOpening"); } else { ElMessage.error(res.msg); } }); };
//关门
const onCloseDoor = () => { const params = {}; const command: OperationCmd = "closeDoor"; taskCmd({ command, params }).then(res => { if (res.success) { // ElMessage.success("指令已发送,请稍等");
doorStatus.value = false; setOnGoingStatus("doorClosing"); } else { ElMessage.error(res.msg); } }); };
//开始加热
const isHeating = ref(false); const onStartHeat = () => { //选择的加热区
if (!selectedTrayList.value.length) { ElMessage.error("请选择目标加热区"); return; } //判断选中的加热区是否有试管架
let existTubeRack = true; //加热区是否设置了加热温度
let hasSetTemp = true; selectedTrayList.value.forEach((item: any) => { let tubeList = item.tubeList; if (!tubeList || !tubeList.length) { existTubeRack = false; } if (!item.temperature) { hasSetTemp = false; } });
if (!existTubeRack) { ElMessage.error("选择的加热区未放置试管架,请重新选择"); return; } if (!hasSetTemp) { ElMessage.error("选择的加热区未设置温度,请设置温度"); return; }
//后台批量发送指令
const cmdList: any = []; selectedTrayList.value.forEach((heatArea: any) => { const params = { heatId: heatArea.id, temperature: heatArea.temperature, }; taskCmd({ command: "startHeat", params }).then(res => { if (res.success) { // ElMessage.success("指令已发送,请稍等");
isHeating.value = true; } else { ElMessage.error(res.msg); } }); }); // onSendCmd("startHeat", cmdList)
};
//停止加热
const onStopHeat = () => { //选择的加热区
if (!selectedTrayList.value.length) { ElMessage.error("请选择目标加热区"); return; } selectedTrayList.value.forEach((heatArea: any) => { const params = { heatId: heatArea.id, }; taskCmd({ command: "stopHeat", params }).then(res => { if (res.success) { // ElMessage.success("指令已发送,请稍等");
isHeating.value = false; } else { ElMessage.error(res.msg); } }); }); };
//开始摇匀
const isSharking = ref(false); const onStartShakingTube = () => { const params = {}; taskCmd({ command: "startShakeUp", params }).then(res => { if (res.success) { // ElMessage.success("指令已发送,请稍等");
isSharking.value = true; setOnGoingStatus("shaking"); } else { ElMessage.error(res.msg); } }); };
//结束摇匀
const onStopShakingTube = () => { const params = {}; taskCmd({ command: "stopShakeUp", params }).then(res => { if (res.success) { // ElMessage.success("指令已发送,请稍等");
isSharking.value = false; } else { ElMessage.error(res.msg); } }); };
//抬起托盘
const onUpTray = () => { if (!selectedTrayList.value.length) { ElMessage.error("请选择目标加热区"); return; } //选择了多个加热区,可托起多个加热区试管加
selectedTrayList.value.forEach((heatAreaItem: any) => { const params = { heatId: heatAreaItem.id, };
taskCmd({ command: "upTray", params }).then(res => { if (res.success) { // ElMessage.success("指令已发送,请稍等");
} else { ElMessage.error(res.msg); } }); }); };
//修改加热区状态 selectedValue: 0 | 1 | 2; // 0为无托盘,1为有托盘,2为托盘抬起
const updateheatAearStatus = (selectedValue: any, heatId: string) => { let heaterList = statusStore.status?.heatArea; if (heaterList) { heaterList.forEach((item: any) => { if (item.heaterId == heatId) { item.trayStatus = selectedValue; } }); const data: any = { ...statusStore.status, heater: [...heaterList], }; statusStore.setStatus(data); } };
const onSendCmd = (command: OperationCmd, params: any) => { //发送加热指令
taskCmd({ command, params }).then(res => { if (res.success) { // ElMessage.success("指令已发送,请稍等");
setOnGoingStatus("movingToAct"); } else { ElMessage.error(res.msg); } }); }; </script> <style lang="scss" scoped> @use "@/assets/style/mixin.scss" as *;
.graphite_home { background: #f6f6f6; display: flex; // @media (min-width: $md) {
flex-direction: column; align-items: stretch; // }
.picture_area { display: flex; flex-direction: column-reverse; margin: 0 12px; } @media (min-width: $lg) { flex-direction: row; align-items: start; .picture_area { display: flex; flex-direction: column; } } } .heat_area { margin: 5px 0;
background: #ffffff; border-radius: 20px;
column-gap: 8px; row-gap: 10px; padding: 1.5rem 0.5rem; min-width: 600px; flex: 1 1 auto; display: grid; grid-template-columns: repeat(3, 1fr); > * { justify-self: center; align-self: center; }
@media (min-width: $md) { column-gap: 12px; row-gap: 20px; height: 47.5rem; }
@media (min-width: $xl) { padding: 4.5rem 1rem; }
.craft_executing_modal { position: absolute; width: 10.5rem; height: 18rem; background: rgb(230, 230, 230); opacity: 0.5; z-index: 9999; } }
.picture_area { display: flex; flex-direction: column; margin: 5px; // margin-left: 1.25rem;
background: #ffffff; border-radius: 20px; padding: 0 1.5rem; @media (min-width: $lg) { flex: 1 1 180px; } @media (min-width: $xl) { flex: 0 0 auto; width: 27rem; }
.graphite_btn_container { margin: 2rem 0; gap: 0.625rem; display: grid; @media (max-width: calc($md - 0.1px)) { grid-template-columns: repeat(2, 1fr); .op_open_door { grid-column: 1/-1; } .op_up_tray { grid-column: 1/-1; } } @media (min-width: $md) and (max-width: calc($lg - 0.1px)) { grid-template-columns: repeat(6, 1fr); > * { grid-column: span 3; } .op_open_door { grid-column: 1/-1; } .op_move_heat { grid-column: span 2; } .op_move_act { grid-column: span 2; } .op_move_exception { grid-column: span 2; } } @media (min-width: $lg) and (max-width: calc($xl - 0.1px)) { grid-template-columns: repeat(2, 1fr); .op_open_door { grid-column: 1/-1; } .op_up_tray { grid-column: 1/-1; } } @media (min-width: $xl) { grid-template-columns: repeat(6, 1fr); > * { grid-column: span 3; } .op_open_door { grid-column: 1/-1; } .op_move_heat { grid-column: span 2; } .op_move_act { grid-column: span 2; } .op_move_exception { grid-column: span 2; } } } }
.btn_size { height: 2.75rem; color: #1989fa; border: 1px solid #1989fa; font-size: 1.25rem; }
.liquid { display: flex; justify-content: center; width: 100%; height: 100%; align-items: center; .addLiquid { width: 70.375rem; height: 52rem; background: #ffffff; } }
.home_tube { width: 13rem; height: 13rem; background: #384d5d; opacity: 1; margin-top: 0.5rem; display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem; padding-top: 0.2rem; border-radius: 1.5rem; .inner-circle { border-radius: 50%; width: 2.5rem; height: 2.5rem; background-color: rgb(212, 212, 212); } }
.task_name { height: 17.25rem; width: 27.5rem; background: #ffffff;
.task_title { font-size: 1.25rem; color: #40474e; margin-left: 1.25rem; margin-top: 1.875rem; }
.task_name_content { margin-top: 1.875rem; margin-left: 1.5rem; font-size: 1.25rem; color: #40474e; display: flex; .task_input { border: 1px solid #dcdcdc; border-radius: 8px; height: 3rem; } }
.task_button { display: flex; justify-content: center; } } .cancel_btn { border: 1px solid rgb(226, 226, 226); width: 5rem; margin-left: 2rem; } </style>
|