石墨仪设备 前端仓库
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

  1. <template>
  2. <div class="component-page p-4 text-title">
  3. <el-tabs type="border-card">
  4. <el-tab-pane label="单一动作">
  5. <div class="frame">
  6. <div class="flex items-center gap-4 flex-wrap">
  7. <label>加热区编号:</label>
  8. <el-select v-model="selectedAreaForTray" placeholder="Select" style="width: 200px">
  9. <el-option v-for="item in areaOptions" :key="item.value" :label="item.label" :value="item.value" />
  10. </el-select>
  11. <label>升降高度:</label>
  12. <input type="number" v-model="trayHeight" class="rounded-sm px-2" />
  13. <span>mm</span>
  14. </div>
  15. <div class="flex gap-4 flex-wrap">
  16. <button class="btn-light px-2 py-1" @click="onCmdClick('upTray')">抬起托盘</button>
  17. <button class="btn-light px-2 py-1" @click="onCmdClick('downTray')">降下托盘</button>
  18. </div>
  19. </div>
  20. <div class="frame">
  21. <div class="flex items-center gap-4 flex-wrap">
  22. X:<input type="number" v-model="x" class="rounded-sm px-1 w-16" /> Y:<input
  23. type="number"
  24. v-model="y"
  25. class="rounded-sm px-1 w-16" />
  26. Z:<input type="number" v-model="z" class="rounded-sm px-1 w-16" />
  27. <label for="">当前位置</label>
  28. <span class="text-warn">{{
  29. `${statusStore.status?.railArm.x || 0},${statusStore.status?.railArm.y || 0},${
  30. statusStore.status?.railArm.z || 0
  31. }`
  32. }}</span>
  33. </div>
  34. <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('moveMachineArm')">移动机械臂</button>
  35. </div>
  36. <div class="frame">
  37. <div class="flex gap-4 flex-wrap">
  38. <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('openClaw')">张开夹爪</button>
  39. <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('closeClaw')">收合夹爪</button>
  40. </div>
  41. </div>
  42. <div class="frame">
  43. <div class="flex items-center gap-4 flex-wrap">
  44. <label for="">加液泵编号:</label>
  45. <input type="number" v-model="pumpId" class="rounded-sm px-2" placeholder="输入加液泵编号" />
  46. <label for="">注入量:</label>
  47. <input type="number" v-model="pumpAmount" class="rounded-sm px-2" placeholder="输入注入量" />
  48. <span>ml</span>
  49. <!-- <label for="">当前容量</label>
  50. <span class="text-warn">50</span> -->
  51. </div>
  52. <div class="flex gap-4 flex-wrap">
  53. <button class="btn-light px-2 py-1" @click="onCmdClick('injectFluid')">注入溶液</button>
  54. </div>
  55. </div>
  56. <div class="frame">
  57. <div class="flex items-center gap-4 flex-wrap">
  58. <label>加热区编号:</label>
  59. <el-select v-model="selectedAreaForHeat" placeholder="Select" style="width: 200px">
  60. <el-option v-for="item in areaOptions" :key="item.value" :label="item.label" :value="item.value" />
  61. </el-select>
  62. <label>温度:</label>
  63. <input type="number" v-model="heatTemperature" class="rounded-sm px-2" placeholder="输入温度" />
  64. <span></span>
  65. </div>
  66. <div class="flex gap-4 flex-wrap">
  67. <button class="btn-light px-2 py-1" @click="onCmdClick('startHeat')">开始加热</button>
  68. <button class="btn-light px-2 py-1" @click="onCmdClick('stopHeat')">停止加热</button>
  69. <!-- <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('keepHeat')">恒温</button> -->
  70. </div>
  71. <div>
  72. <label for="">当前温度</label>
  73. <span class="text-warn">{{ (statusStore.status?.heater || []).map(h => h.current).join(",") }}</span>
  74. </div>
  75. </div>
  76. <div class="frame">
  77. <div class="flex items-center gap-4 flex-wrap">
  78. <label>摇匀速度:</label>
  79. <input type="number" v-model="shakeUpSpeed" class="rounded-sm px-2" />
  80. </div>
  81. <div class="flex gap-4 flex-wrap">
  82. <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('startShakeUp')">开始摇匀</button>
  83. <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('stopShakeUp')">结束摇匀</button>
  84. <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('takePhoto')">拍照</button>
  85. </div>
  86. </div>
  87. </el-tab-pane>
  88. <el-tab-pane label="复合动作">
  89. <div class="tabFrame">
  90. <div class="frame">
  91. <div class="flex items-center gap-4 flex-wrap">
  92. <label>加热区编号:</label>
  93. <el-select v-model="selectedArea" placeholder="Select" style="width: 200px">
  94. <el-option
  95. v-for="item in areaOptions"
  96. :key="item.value"
  97. :label="item.label"
  98. :value="item.value" />
  99. </el-select>
  100. </div>
  101. <div class="flex gap-4 flex-wrap">
  102. <button class="btn-light px-2 py-1" @click="onCmdClick('takeOffCap')">取下拍子</button>
  103. <button class="btn-light px-2 py-1" @click="onCmdClick('putBackCap')">装回拍子</button>
  104. <button class="btn-light px-2 py-1" @click="onCmdClick('moveToActionArea')">
  105. 移至操作区(加液摇匀拍照)
  106. </button>
  107. <button class="btn-light px-2 py-1" @click="onCmdClick('moveToHeatArea')">
  108. (从操作区)移至加热区
  109. </button>
  110. </div>
  111. </div>
  112. <div class="frame">
  113. <div class="flex items-center gap-4">
  114. <label>源位置:</label>
  115. <input type="number" class="rounded-sm px-2" />
  116. <label>目标位置:</label>
  117. <input type="number" class="rounded-sm px-2" />
  118. </div>
  119. <button class="btn-light px-2 py-1 min-w-20" @click="onCmdClick('moveTube')">移动试管</button>
  120. </div>
  121. </div>
  122. </el-tab-pane>
  123. <el-tab-pane label="校准">
  124. <h1 class="text-lg font-medium py-2">加热区坐标</h1>
  125. <div
  126. v-for="config in settingStore.heatAreaConfig"
  127. :key="config.code"
  128. class="flex items-center gap-4 flex-wrap mb-2">
  129. <p class="min-w-6">{{ config.name }}</p>
  130. <span>X:</span>
  131. <input type="number" class="rounded-sm px-1 w-16" v-model="config.valueObj.x" />
  132. <span>Y:</span>
  133. <input type="number" class="rounded-sm px-1 w-16" v-model="config.valueObj.y" />
  134. <span>Z:</span>
  135. <input type="number" class="rounded-sm px-1 w-16" v-model="config.valueObj.z" />
  136. </div>
  137. <h1 class="text-lg font-medium py-2">操作区加液摇匀拍照坐标</h1>
  138. <div class="flex items-center gap-4 flex-wrap mb-2">
  139. <span>X:</span>
  140. <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.actionAreaConfig.valueObj.x" />
  141. <span>Y:</span>
  142. <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.actionAreaConfig.valueObj.y" />
  143. <span>Z:</span>
  144. <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.actionAreaConfig.valueObj.z" />
  145. </div>
  146. <h1 class="text-lg font-medium py-2">拍子区坐标</h1>
  147. <div class="flex items-center gap-4 flex-wrap mb-2">
  148. <span>X:</span>
  149. <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.capAreaConfig.valueObj.x" />
  150. <span>Y:</span>
  151. <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.capAreaConfig.valueObj.y" />
  152. <span>Z:</span>
  153. <input type="number" class="rounded-sm px-1 w-16" v-model="settingStore.capAreaConfig.valueObj.z" />
  154. </div>
  155. <h1 class="text-lg font-medium py-1">加液</h1>
  156. <div class="flex items-center gap-4 flex-wrap">
  157. <p>1毫升对应</p>
  158. <input type="number" class="rounded-sm px-1 w-16" />
  159. </div>
  160. <footer class="flex justify-end mt-4">
  161. <button class="btn-dark px-2 py-1 min-w-20" @click="saveConfig">保存</button>
  162. </footer>
  163. </el-tab-pane>
  164. </el-tabs>
  165. </div>
  166. </template>
  167. <script setup lang="ts">
  168. import { CmdDescMap, debugCmd, type DebugCmd } from "@/services/debug/debugApi";
  169. import { createWebSocket, sharedWsUrl } from "@/services/socket";
  170. import { updateConfig } from "@/services/sysConfig/sysConfig";
  171. import { useSettingStore } from "@/stores/setting";
  172. import { showToast } from "vant";
  173. import { computed, onMounted, onUnmounted, ref } from "vue";
  174. import { ElMessage } from "element-plus";
  175. import { useStatusStore } from "@/stores/status";
  176. import { getTxnRecord } from "@/services/txn";
  177. const settingStore = useSettingStore();
  178. const statusStore = useStatusStore();
  179. const selectedArea = ref(2);
  180. const selectedAreaForTray = ref(2);
  181. const selectedAreaForHeat = ref(2);
  182. const heatTemperature = ref(0);
  183. const trayHeight = ref(0);
  184. const x = ref(0);
  185. const y = ref(0);
  186. const z = ref(0);
  187. const pumpId = ref(1);
  188. const pumpAmount = ref(10);
  189. const shakeUpSpeed = ref(5);
  190. const areaOptions = computed(() => {
  191. return settingStore.heatAreaConfig.map(c => ({ label: c.name, value: c.id }));
  192. });
  193. onMounted(() => {
  194. const wsClient = createWebSocket(sharedWsUrl);
  195. const subscription = wsClient.dataOb.subscribe(data => {
  196. console.log(data);
  197. if (data.type === "cmd") {
  198. const cmdInfo = getTxnRecord(data.data.commandId, "debug");
  199. if (cmdInfo) {
  200. const cmdName = CmdDescMap[cmdInfo.command];
  201. const result = data.data.success ? "执行完毕" : "执行失败";
  202. ElMessage({
  203. message: `${cmdName} ${result}`,
  204. type: data.data.success ? "success" : "error",
  205. });
  206. }
  207. } else if (data.type === "status") {
  208. statusStore.setStatus(data.data);
  209. } else if (data.type === "warn") {
  210. ElMessage({
  211. message: data.data.message,
  212. type: "warning",
  213. });
  214. }
  215. });
  216. wsClient.connect();
  217. onUnmounted(() => {
  218. subscription.unsubscribe();
  219. });
  220. });
  221. function onCmdClick(command: DebugCmd) {
  222. let params: Record<string, any> = {};
  223. if (command === "upTray") {
  224. params = { areaId: selectedAreaForTray.value, height: trayHeight.value };
  225. } else if (command === "downTray") {
  226. params = { areaId: selectedAreaForTray.value, height: trayHeight.value };
  227. } else if (command === "moveMachineArm") {
  228. params = { x: x.value, y: y.value, z: z.value };
  229. } else if (command === "injectFluid") {
  230. params = { areaId: pumpId.value, volume: pumpAmount.value };
  231. } else if (command === "startHeat") {
  232. params = { areaId: selectedAreaForHeat.value, temperature: heatTemperature.value };
  233. } else if (command === "stopHeat") {
  234. params = { areaId: selectedAreaForHeat.value };
  235. } else if (command === "startShakeUp") {
  236. params = { speed: shakeUpSpeed.value };
  237. } else if (
  238. command === "takeOffCap" ||
  239. command === "putBackCap" ||
  240. command === "moveToActionArea" ||
  241. command === "moveToHeatArea"
  242. ) {
  243. params = { areaId: selectedArea.value };
  244. }
  245. debugCmd({ command, params }).then(res => {
  246. if (res.success) {
  247. // showToast("已执行,请稍等");
  248. } else {
  249. showToast(res.msg);
  250. }
  251. });
  252. }
  253. function saveConfig() {
  254. const heatCfg = settingStore.heatAreaConfig.map(c => ({
  255. id: c.id,
  256. value: `${c.valueObj.x},${c.valueObj.y},${c.valueObj.z}`,
  257. }));
  258. const actionCfg = {
  259. id: settingStore.actionAreaConfig.id || 0,
  260. value: `${settingStore.actionAreaConfig.valueObj.x},${settingStore.actionAreaConfig.valueObj.y},${settingStore.actionAreaConfig.valueObj.z}`,
  261. };
  262. const capCfg = {
  263. id: settingStore.capAreaConfig.id || 0,
  264. value: `${settingStore.capAreaConfig.valueObj.x},${settingStore.capAreaConfig.valueObj.y},${settingStore.capAreaConfig.valueObj.z}`,
  265. };
  266. updateConfig([...heatCfg, actionCfg, capCfg]).then(res => {
  267. if (res.success) {
  268. showToast("保存成功");
  269. }
  270. });
  271. }
  272. </script>
  273. <style lang="scss" scoped>
  274. .tabFrame {
  275. overflow: auto;
  276. height: calc(100vh - var(--headerHeight) - var(--footerHeight) - 90px);
  277. }
  278. .frame {
  279. border-bottom: solid 1px #ddd;
  280. padding: 1rem 0.5rem;
  281. display: flex;
  282. flex-direction: column;
  283. align-items: start;
  284. gap: 1rem;
  285. }
  286. input {
  287. background-color: #f5f5f5;
  288. height: 1.75rem;
  289. }
  290. </style>