|
|
<template> <div class="component-page p-4 text-title"> <el-tabs type="border-card"> <el-tab-pane label="单一动作"> <div class="frame"> <div class="flex items-center gap-4 flex-wrap"> <label>加热区编号:</label> <el-select v-model="selectedAreaForTray" placeholder="Select" style="width: 200px"> <el-option v-for="item in areaOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> <label>升降高度:</label> <input type="number" v-model="trayHeight" class="rounded-sm px-2" /> <span>mm</span> </div> <div class="flex gap-4 flex-wrap"> <button class="btn-light px-2 py-1" @click="onCmdClick('upTray')">抬起托盘</button> <button class="btn-light px-2 py-1" @click="onCmdClick('downTray')">降下托盘</button> </div> </div> <div class="frame"> <div class="flex items-center gap-4 flex-wrap"> X:<input type="number" v-model="x" class="rounded-sm px-1 w-16" /> Y:<input type="number" v-model="y" class="rounded-sm px-1 w-16" /> Z:<input type="number" v-model="z" class="rounded-sm px-1 w-16" /> <label for="">当前位置:</label> <span class="text-warn">{{ `${statusStore.status?.railArm.x || 0},${statusStore.status?.railArm.y || 0},${ statusStore.status?.railArm.z || 0 }`
}}</span> </div> <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('moveMachineArm')">移动机械臂</button> </div> <div class="frame"> <div class="flex gap-4 flex-wrap"> <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('openClaw')">张开夹爪</button> <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('closeClaw')">收合夹爪</button> </div> </div> <div class="frame"> <div class="flex items-center gap-4 flex-wrap"> <label for="">加液泵编号:</label> <input type="number" v-model="pumpId" class="rounded-sm px-2" placeholder="输入加液泵编号" /> <label for="">注入量:</label> <input type="number" v-model="pumpAmount" class="rounded-sm px-2" placeholder="输入注入量" /> <span>ml</span> <!-- <label for="">当前容量:</label> <span class="text-warn">50</span> --> </div> <div class="flex gap-4 flex-wrap"> <button class="btn-light px-2 py-1" @click="onCmdClick('injectFluid')">注入溶液</button> </div> </div> <div class="frame"> <div class="flex items-center gap-4 flex-wrap"> <label>加热区编号:</label> <el-select v-model="selectedAreaForHeat" placeholder="Select" style="width: 200px"> <el-option v-for="item in areaOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> <label>温度:</label> <input type="number" v-model="heatTemperature" class="rounded-sm px-2" placeholder="输入温度" /> <span>℃</span> </div> <div class="flex gap-4 flex-wrap"> <button class="btn-light px-2 py-1" @click="onCmdClick('startHeat')">开始加热</button> <button class="btn-light px-2 py-1" @click="onCmdClick('stopHeat')">停止加热</button> <!-- <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('keepHeat')">恒温</button> --> </div> <div> <label for="">当前温度:</label> <span class="text-warn">{{ (statusStore.status?.heater || []).map(h => h.current).join(",") }}</span> </div> </div>
<div class="frame"> <div class="flex items-center gap-4 flex-wrap"> <label>摇匀速度:</label> <input type="number" v-model="shakeUpSpeed" class="rounded-sm px-2" /> </div> <div class="flex gap-4 flex-wrap"> <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('startShakeUp')">开始摇匀</button> <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('stopShakeUp')">结束摇匀</button> <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('takePhoto')">拍照</button> </div> </div> </el-tab-pane>
<el-tab-pane label="复合动作"> <div class="tabFrame"> <div class="frame"> <div class="flex items-center gap-4 flex-wrap"> <label>加热区编号:</label> <el-select v-model="selectedArea" placeholder="Select" style="width: 200px"> <el-option v-for="item in areaOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </div> <div class="flex gap-4 flex-wrap"> <button class="btn-light px-2 py-1" @click="onCmdClick('takeOffCap')">取下拍子</button> <button class="btn-light px-2 py-1" @click="onCmdClick('putBackCap')">装回拍子</button> <button class="btn-light px-2 py-1" @click="onCmdClick('moveToActionArea')"> 移至操作区(加液、摇匀、拍照) </button> <button class="btn-light px-2 py-1" @click="onCmdClick('moveToHeatArea')"> (从操作区)移至加热区 </button> </div> </div>
<div class="frame"> <div class="flex items-center gap-4"> <label>源位置:</label> <input type="number" class="rounded-sm px-2" /> <label>目标位置:</label> <input type="number" class="rounded-sm px-2" /> </div> <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('moveTube')">移动试管</button> </div> </div> </el-tab-pane>
<el-tab-pane label="校准"> <h1 class="text-lg font-medium py-2">加热区坐标:</h1> <div v-for="config in settingStore.heatAreaConfig" :key="config.code" class="flex items-center gap-4 flex-wrap mb-2"> <p class="min-w-6">{{ config.name }}</p> <span>X:</span> <input type="number" class="rounded-sm px-1 w-16" v-model="config.valueObj.x" /> <span>Y:</span> <input type="number" class="rounded-sm px-1 w-16" v-model="config.valueObj.y" /> <span>Z:</span> <input type="number" class="rounded-sm px-1 w-16" v-model="config.valueObj.z" /> </div> <h1 class="text-lg font-medium py-2">操作区(加液、摇匀、拍照)坐标:</h1> <div class="flex items-center gap-4 flex-wrap mb-2"> <span>X:</span> <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.actionAreaConfig.valueObj.x" /> <span>Y:</span> <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.actionAreaConfig.valueObj.y" /> <span>Z:</span> <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.actionAreaConfig.valueObj.z" /> </div> <h1 class="text-lg font-medium py-2">拍子区坐标:</h1> <div class="flex items-center gap-4 flex-wrap mb-2"> <span>X:</span> <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.capAreaConfig.valueObj.x" /> <span>Y:</span> <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.capAreaConfig.valueObj.y" /> <span>Z:</span> <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.capAreaConfig.valueObj.z" /> </div> <h1 class="text-lg font-medium py-1">加液:</h1> <div class="flex items-center gap-4 flex-wrap"> <p>1毫升对应</p> <input type="number" class="rounded-sm px-1 w-16" /> 步 </div> <footer class="flex justify-end mt-4"> <button class="btn-dark px-2 py-1 min-w-20" @click="saveConfig">保存</button> </footer> </el-tab-pane> </el-tabs> </div> </template> <script setup lang="ts"> import { CmdDescMap, debugCmd, type DebugCmd } from "@/services/debug/debugApi"; import { createWebSocket, sharedWsUrl } from "@/services/socket"; import { updateConfig } from "@/services/sysConfig/sysConfig"; import { useSettingStore } from "@/stores/setting"; import { showToast } from "vant"; import { computed, onMounted, onUnmounted, ref } from "vue"; import { ElMessage } from "element-plus"; import { useStatusStore } from "@/stores/status"; import { getTxnRecord } from "@/services/txn";
const settingStore = useSettingStore(); const statusStore = useStatusStore();
const selectedArea = ref(2); const selectedAreaForTray = ref(2); const selectedAreaForHeat = ref(2); const heatTemperature = ref(0); const trayHeight = ref(0);
const x = ref(0); const y = ref(0); const z = ref(0);
const pumpId = ref(1); const pumpAmount = ref(10);
const shakeUpSpeed = ref(5);
const areaOptions = computed(() => { return settingStore.heatAreaConfig.map(c => ({ label: c.name, value: c.id })); });
onMounted(() => { const wsClient = createWebSocket(sharedWsUrl); const subscription = wsClient.dataOb.subscribe(data => { console.log(data); if (data.type === "cmd") { const cmdInfo = getTxnRecord(data.data.commandId, "debug"); if (cmdInfo) { const cmdName = CmdDescMap[cmdInfo.command]; const result = data.data.success ? "执行完毕" : "执行失败"; ElMessage({ message: `${cmdName} ${result}`, type: data.data.success ? "success" : "error", }); } } else if (data.type === "status") { statusStore.setStatus(data.data); } else if (data.type === "warn") { ElMessage({ message: data.data.message, type: "warning", }); } }); wsClient.connect();
onUnmounted(() => { subscription.unsubscribe(); }); });
function onCmdClick(command: DebugCmd) { let params: Record<string, any> = {}; if (command === "upTray") { params = { areaId: selectedAreaForTray.value, height: trayHeight.value }; } else if (command === "downTray") { params = { areaId: selectedAreaForTray.value, height: trayHeight.value }; } else if (command === "moveMachineArm") { params = { x: x.value, y: y.value, z: z.value }; } else if (command === "injectFluid") { params = { areaId: pumpId.value, volume: pumpAmount.value }; } else if (command === "startHeat") { params = { areaId: selectedAreaForHeat.value, temperature: heatTemperature.value }; } else if (command === "stopHeat") { params = { areaId: selectedAreaForHeat.value }; } else if (command === "startShakeUp") { params = { speed: shakeUpSpeed.value }; } else if ( command === "takeOffCap" || command === "putBackCap" || command === "moveToActionArea" || command === "moveToHeatArea" ) { params = { areaId: selectedArea.value }; } debugCmd({ command, params }).then(res => { if (res.success) { // showToast("已执行,请稍等");
} else { showToast(res.msg); } }); }
function saveConfig() { const heatCfg = settingStore.heatAreaConfig.map(c => ({ id: c.id, value: `${c.valueObj.x},${c.valueObj.y},${c.valueObj.z}`, })); const actionCfg = { id: settingStore.actionAreaConfig.id || 0, value: `${settingStore.actionAreaConfig.valueObj.x},${settingStore.actionAreaConfig.valueObj.y},${settingStore.actionAreaConfig.valueObj.z}`, }; const capCfg = { id: settingStore.capAreaConfig.id || 0, value: `${settingStore.capAreaConfig.valueObj.x},${settingStore.capAreaConfig.valueObj.y},${settingStore.capAreaConfig.valueObj.z}`, }; updateConfig([...heatCfg, actionCfg, capCfg]).then(res => { if (res.success) { showToast("保存成功"); } }); } </script>
<style lang="scss" scoped> .tabFrame { overflow: auto; height: calc(100vh - var(--headerHeight) - var(--footerHeight) - 90px); } .frame { border-bottom: solid 1px #ddd; padding: 1rem 0.5rem; display: flex; flex-direction: column; align-items: start; gap: 1rem; } input { background-color: #f5f5f5; height: 1.75rem; } </style>
|