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.
305 lines
11 KiB
305 lines
11 KiB
<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>
|