Browse Source

完善执行工艺

master
LiLongLong 5 months ago
parent
commit
8edea9484b
  1. 3
      src/App.vue
  2. 1
      src/assets/executing.svg
  3. 1
      src/assets/ready.svg
  4. 2
      src/components/OverlayModal.vue
  5. 1
      src/services/globalCmd/globalCmd.ts
  6. 4
      src/services/ore/oreManage.ts
  7. 9
      src/services/socket.ts
  8. 10
      src/services/task/task.ts
  9. 16
      src/services/txn.ts
  10. 15
      src/stores/craft.ts
  11. 88
      src/views/graphite/components/AddLiquid.vue
  12. 83
      src/views/graphite/components/HeatPosition.vue
  13. 121
      src/views/graphite/index.vue

3
src/App.vue

@ -7,6 +7,7 @@ import { getConfig, getContainerList } from "./services/sysConfig/sysConfig";
import { useSettingStore } from "./stores/setting";
import { getLiquidList } from "./services/liquid/liquidManage";
import { useStatusStore } from "./stores/status";
import { useCraftStore } from "./stores/craft";
import { ElMessage } from "element-plus";
import { getUserList } from "./services/user/userManager";
import { useUserStore } from "./stores/user";
@ -15,6 +16,7 @@ const router = useRouter();
const settingStore = useSettingStore();
const statusStore = useStatusStore();
const userStore = useUserStore();
const craftStore = useCraftStore()
const wsClient = createWebSocket(sharedWsUrl);
wsClient.dataOb.subscribe(data => {
@ -27,6 +29,7 @@ wsClient.dataOb.subscribe(data => {
});
} else if (data.type === "crafts") {
console.log("crafts:", data);
craftStore.setCraftInfo(data.data);
} else if (data.type === "container") {
settingStore.setContainerConf(data.data.containerList);
}

1
src/assets/executing.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="70" height="70" viewBox="0 0 70 70"><defs><clipPath id="master_svg0_685_8843"><rect x="0" y="0" width="70" height="70" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_685_8843)"><g><path d="M35,70C15.6712,70,0,54.3288,0,35C0,15.6712,15.6712,0,35,0C54.3288,0,70,15.6712,70,35C70,54.3288,54.3288,70,35,70ZM27.195,48.7433C28.6189,50.1676,30.9278,50.1676,32.3517,48.7433L54.4542,26.6408C55.8781,25.2169,55.8781,22.9081,54.4542,21.4842C53.0302,20.0602,50.7215,20.0602,49.2975,21.4842L29.6158,40.8508L20.8075,32.0425C19.3838,30.6166,17.0736,30.6158,15.6488,32.0405C14.2241,33.4653,14.225,35.7755,15.6508,37.1992L27.195,48.7433Z" fill="#14A656" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/ready.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_685_8855"><rect x="0" y="0" width="80" height="80" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_685_8855)"><g><path d="M40.00636015625,5.01953125C20.68606015625,5.01953125,5.01416254044,20.69143125,5.01416254044,40.01173125C5.01416254044,59.33203125,20.68606015625,74.99613125,40.00636015625,74.99613125C59.32666015625,74.99613125,74.99856015625,59.32423125,74.99856015625,40.00393125C74.99856015625,20.683631249999998,59.32666015625,5.01953125,40.00636015625,5.01953125ZM21.97506015625,45.97263125C18.678260156249998,45.97263125,16.00636015625,43.30073125,16.00636015625,40.00393125C16.00636015625,36.70703125,18.678260156249998,34.03513125,21.97506015625,34.03513125C25.27196015625,34.03513125,27.94386015625,36.70703125,27.94386015625,40.00393125C27.94386015625,43.30073125,25.27196015625,45.97263125,21.97506015625,45.97263125ZM39.92046015625,45.97263125C36.62356015625,45.97263125,33.95166015625,43.30073125,33.95166015625,40.00393125C33.95166015625,36.70703125,36.62356015625,34.03513125,39.92046015625,34.03513125C43.21726015625,34.03513125,45.88916015625,36.70703125,45.88916015625,40.00393125C45.88916015625,43.30073125,43.21726015625,45.97263125,39.92046015625,45.97263125ZM57.86576015625,45.97263125C54.56886015625,45.97263125,51.89696015625,43.30073125,51.89696015625,40.00393125C51.89696015625,36.70703125,54.56886015625,34.03513125,57.86576015625,34.03513125C61.16256015625,34.03513125,63.83446015625,36.70703125,63.83446015625,40.00393125C63.83446015625,43.30073125,61.16256015625,45.97263125,57.86576015625,45.97263125Z" fill="#EE8223" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

2
src/components/OverlayModal.vue

@ -21,7 +21,7 @@
position: absolute;
top: 0px;
left: 0px;
z-index: 999
z-index: 112
}
.overlay_modal{
display: flex;

1
src/services/globalCmd/globalCmd.ts

@ -118,6 +118,7 @@ export const CmdDescMap: { [k in OperationCmd]: string } = {
moveTube: "移动试管",
openDoor: "开门",
closeDoor: "关门",
};
export type OperationCmd =

4
src/services/ore/oreManage.ts

@ -41,3 +41,7 @@ export function getCraftList(ids: string) {
export function craftStart(params:any) {
return httpRequest<BaseResponse>({ url: `/api/crafts/start`, method: "POST", params });
}
export function craftStop(params:any) {
return httpRequest<BaseResponse>({ url: `/api/crafts/stop`, method: "POST", params });
}

9
src/services/socket.ts

@ -116,6 +116,15 @@ export type StatusDatagram = {
};
};
export type CraftState = {
type: "crafts"
data: {
heatId: string | number
methodIndex: string| number
status: string| number
}
}
export type ContainerDatagram = {
type: "container",
data: {

10
src/services/task/task.ts

@ -1,5 +1,5 @@
import httpRequest, { type BaseResponse } from "../httpRequest";
import { addTxnRecord } from "../txn";
import { addTxnRecord, injectFluidsRecord } from "../txn";
import type { OperationCmd } from "../globalCmd/globalCmd";
@ -21,5 +21,11 @@ export function getIngTask() {
//批量加热
export function startHeat(params: { command: OperationCmd; params: [] }) {
const commandId = addTxnRecord({ ...params, category: "task" });
return httpRequest<BaseResponse<string>>({ url: "/api/cmd/startHeat", params, method: "POST" });
return httpRequest<BaseResponse<string>>({ url: "/api/cmd/startHeat", params: { ...params, commandId }, method: "POST" });
}
//批量加液
export function injectFluid(params:any) {
const commandId = injectFluidsRecord({ injectFluids:params.injectFluids, command:'', category: "task" });
return httpRequest<BaseResponse<string>>({ url: "/api/cmd/injectFluid", params: { ...params, commandId }, method: "POST" });
}

16
src/services/txn.ts

@ -24,11 +24,17 @@ type DebugCmdRecord = {
type TaskCmdRecord = {
category: "task";
command: OperationCmd;
command: OperationCmd | null;
params: Record<string, any>;
}
type TxnRecord = DebugCmdRecord | TaskCmdRecord
type TaskCmdInjectFluidsRecord = {
category: "task";
command: '',
injectFluids: [];
}
type TxnRecord = DebugCmdRecord | TaskCmdRecord | TaskCmdInjectFluidsRecord
const txnCmdMap: Record<string, TxnRecord> = {};
@ -38,6 +44,12 @@ export function addTxnRecord(val: TxnRecord) {
return txn;
}
export function injectFluidsRecord(val: TaskCmdInjectFluidsRecord){
const txn = generateTxnNo().toString();
txnCmdMap[txn] = val;
return txn;
}
export function getTxnRecord(txn: string, category: TxnRecord["category"]) {
const record = txnCmdMap[txn];
// 只有属于指定category时,才返回,且返回后删除记录,节约内存

15
src/stores/craft.ts

@ -0,0 +1,15 @@
import { ref, computed } from "vue";
import { defineStore } from "pinia";
import * as R from "ramda";
import type { CraftState } from "@/services/socket";
export const useCraftStore = defineStore("craft", () => {
const craftInfo = ref<CraftState["data"] | undefined>();
const setCraftInfo = (data: CraftState["data"]) => {
if (!R.equals(craftInfo.value, data)) {
craftInfo.value = data;
}
};
return { craftInfo, setCraftInfo };
});

88
src/views/graphite/components/AddLiquid.vue

@ -1,22 +1,29 @@
<template>
<div>
<div class="title">添加溶液</div>
<!--选择试管区-->
<section class="main">
<div class="liquid_tube">
<div>
<div class="select_tube_title">
请选择加液试管
</div>
<div class="select_tube">
<div
class="inner-circle"
v-for="(tubeItem, index) in tubeList"
:key="index"
:style="{'border': `2px solid ${tubeItem.color}`,'background':tubeItem.selectedBackgroudColor || tubeItem.backgroudColor}"
@click="onChooseTube(tubeItem, index)">
<div class="liquid_tube">
<div class="select_tube">
<div
class="inner-circle"
v-for="(tubeItem, index) in tubeList"
:key="index"
:style="{'border': `2px solid ${tubeItem.color}`,'background':tubeItem.selectedBackgroudColor || tubeItem.backgroudColor}"
@click="onChooseTube(tubeItem, index)">
</div>
</div>
</div>
<div class="tube_num">已选择{{ selectedTubeList.length }}个试管</div>
<div class="queue_list">
<el-row class="liquid_queue" v-for="item in tubeQueue">
<el-col :span="23">试管{{ item.tubeNum }}-{{ item.name }}添加{{ item.volume }}ml</el-col>
<el-col :span="1" @click="delQueueSolution(item)">X</el-col>
</el-row>
</div>
</div>
<div class="liquid_main">
@ -49,12 +56,6 @@
</div>
</div>
</div>
<!-- <div class="liquid_queue">
<van-tag style="height: 2.5rem; border-radius: 10px;margin-left: 10px;" v-for="item in tubeQueue" closeable size="medium" type="primary" @close="delQueueSolution(item)">
{{ item.tubeCode }} : {{ item.name }}添加{{ item.volume }}ml
</van-tag>
</div> -->
</section>
<footer class="liquid_button">
<van-button class="btn" @click="onConfirm" type="primary">确认添加</van-button>
@ -123,7 +124,7 @@
const onChooseTube = (tubeItem, index) => {
if(!tubeItem.id)return
//
tubeItem.tubeCode = index + 1
tubeItem.tubeNum = index + 1
//
if(tubeItem.isSelectedByLiquid){//
tubeItem.color = emptyColor
@ -157,7 +158,6 @@
const onSolutionToTube = ({solutionId, name, solutionNum})=> {
if(!solutionNum)return;
//
console.log('tubeList---', tubeList)
tubeList.value.forEach((item) => {
if(item.isSelectedByLiquid){
const tubeId = generateRandomNumber()
@ -174,7 +174,7 @@
solutionId,
volume:solutionNum,
name,
tubeCode: item.tubeCode,
tubeNum: item.tubeNum,
backgroudColor:item.backgroudColor
})
}
@ -203,36 +203,41 @@
.main{
display: flex;
height: 45rem;
height: 44rem;
}
.select_tube_title{
color: #323233;
font-size: 1.125rem;
width: 8rem;
height: .75rem;
margin-top: 2rem;
margin-left: 2.875rem;
}
.liquid_tube{
height: 16.25rem;
width: 2080px;
.select_tube_title{
color: #323233;
font-size: 1.125rem;
width: 8rem;
height: .75rem;
margin-top: 2.25rem;
margin-left: 2.875rem;
}
height: 14.25rem;
padding-left: 9.375rem;
padding-right: 9.375rem;
.select_tube{
width: 15.5rem;
height: 15.5rem;
width: 13.75rem;
height: 13.75rem;
background: #384D5D;
opacity: 1;
margin-top: 0.5rem;
margin-left: 1.2rem;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.7rem;
gap: 0.3rem;
padding: 0.6rem;
border-radius: 1.5rem;
margin-top:1rem;
.inner-circle {
border-radius: 50%;
width: 3rem;
height: 3rem;
width: 2.875rem;
height: 2.875rem;
background-color: rgb(212, 212, 212);
border: 2px solid rgb(212, 212, 212);
}
@ -331,11 +336,20 @@
}
.queue_list{
overflow-y: auto;
height: 22rem;
}
.liquid_queue{
display: flex;
height: 4.125rem;
height: 2.875rem;
background: rgba(25, 137, 250, 0.12);
align-items: center;
margin-left: 2rem;
border-radius: 15px;
padding-left: 2rem;
margin-top: 1rem;
}
.liquid_button{
margin-left: 35%;

83
src/views/graphite/components/HeatPosition.vue

@ -24,12 +24,14 @@
:style="{ background: tube.backgroudColor || 'rgb(212, 212, 212)' }"
></div>
<!--执行中状态的遮罩层-->
<div class="craft_executing_modal" v-if="trayInfo.executing_craft">
<div class="craft_executing_modal" v-if="trayInfo.craftInfo?.id" :style="{background: !trayInfo.executing_craft ? '#FFDFC2' : '#8BFFBF'}">
<!--trayInfo.executing_craft 已选择工艺未执行-->
<div class="loading">
<img :src="LoadingSvg" />
<img v-if="trayInfo.executing_craft" :src="ExecutingSvg"/>
<img v-else :src="ReadySvg" />
</div>
<div class="tray_craft">{{ trayInfo.craftInfo.name }}</div>
<div class="tray_exce">工艺执行中</div>
<div class="tray_craft" :style="{color: !trayInfo.executing_craft ? readyColor : executingColor}">{{ trayInfo.craftInfo.name }}</div>
<div class="tray_exce" :style="{color: !trayInfo.executing_craft ? readyColor : executingColor}">{{ !trayInfo.executing_craft ? '工艺等待执行' : '工艺执行完毕'}}</div>
</div>
</section>
@ -47,7 +49,7 @@
<div class="flex items-center py-4 justify-between px-2">
<div class="temp_text">{{ trayInfo.heatAearStatus.temperature }}</div>
<button
class="btn-light text-sm px-4"
class="btn-light text-sm px-4 text_choose"
@click.stop="onSelectTray"
>
选择
@ -103,7 +105,9 @@
import { ref, watch, onMounted, getCurrentInstance } from "vue";
import OverlayModal from "@/components/OverlayModal.vue";
import CraftList from "./CraftList.vue";
import LoadingSvg from "@/assets/loading.svg";
import ReadySvg from "@/assets/ready.svg";
import ExecutingSvg from "@/assets/executing.svg";
const tubeList = ref<any>([]);
const props = defineProps({
heatInfo: Object,
@ -111,6 +115,8 @@ const props = defineProps({
});
const emits = defineEmits(["onSelectedTray", "onSetHeatAreaTemp", 'onSelectCraft']);
const selectedBackgroundColor = '#189952'
const readyColor = '#EE8223'
const executingColor = '#14A656'
onMounted(() => {
for(let i = 0 ; i<16; i++){
tubeList.value.push({
@ -181,6 +187,7 @@ const onConfirm = () => {
//
const onHandleTube = (tubeInfo: any, index:number) => {
if(!trayInfo.value.heatAearStatus.trayStatus)return;
//UI
if(trayInfo.value.heatAearStatus.trayStatus && tubeInfo.isSelected){
tubeInfo.backgroudColor = ''
@ -238,34 +245,7 @@ const onHandleSelectedCraft = (craftData:any) => {
padding-top: 0.5rem;
border-radius: 0.5rem;
.craft_executing_modal {
position: relative;
width: 10rem;
height: 18rem;
background: rgba(2, 86, 255, 0.12);
opacity: 0.5;
z-index: 9999;
margin-top: -11rem;
.loading {
margin-top: 4rem;
display: flex;
justify-content: center;
}
.tray_craft {
font-size: 1rem;
margin-top: 0.5rem;
margin-left: 3rem;
font-weight: bold;
color: #4d6882;
}
.tray_exce {
font-size: 1.2rem;
margin-left: 1.8rem;
font-weight: bold;
color: #4d6882;
}
}
}
.inner-circle {
border-radius: 50%;
@ -376,4 +356,41 @@ const onHandleSelectedCraft = (craftData:any) => {
margin-top: -10px;
}
}
.text_choose{
position: relative;
z-index: 111;
background: #6893ff;
color: #ffffff;
}
.craft_executing_modal {
position: relative;
width: 11.2rem;
height: 17.8rem;
padding-top: 0.5rem;
border-radius: 0.5rem;
opacity: 0.8;
z-index: 99;
margin-top: -11.5rem;
.loading {
margin-top: 4rem;
display: flex;
justify-content: center;
}
.tray_craft {
font-size: .75rem;
margin-top: 0.5rem;
font-weight: bold;
display: flex;
justify-content: center;
color: #EE8223;
}
.tray_exce {
font-size: 1rem;
font-weight: bold;
color: #EE8223;
display: flex;
justify-content: center;
}
}
</style>

121
src/views/graphite/index.vue

@ -46,10 +46,10 @@
size="large"
class="btn_size op_select_craft"
@click="onChooseCaft"
>选择工艺</van-button
>{{ craftName }}</van-button
>
<van-button size="large" class="btn_size op_exec_craft" @click="onCraftStart"
>执行工艺</van-button
<van-button size="large" class="btn_size op_exec_craft" @click="onCraftStart()"
>{{ exeCraftName }}</van-button
>
<van-button
size="large"
@ -111,7 +111,6 @@
<input v-model="taskName" placeholder="实验名称" class="task_input" />
<div v-if="!taskName" style="color:red;font-size: 1rem;">请输入实验名称</div>
</div>
</div>
<br/>
@ -124,14 +123,14 @@
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onUnmounted } from "vue";
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 { graphiteMock } from "./components/mock";
import { injectFluid } from "@/services/task/task";
import { useStatusStore } from "@/stores/status";
import {getIngTask, saveTaskName, stopTask} from '@/services/task/task';
import {
@ -139,24 +138,33 @@ import {
taskCmd,
type OperationCmd,
} from "@/services/globalCmd/globalCmd";
import { craftStart } from "@/services/ore/oreManage";
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);
const switchModule = ref(false);
let tubeList = reactive<any>([]);
const selectedColor = "#4F85FB";
const emptyColor = "#FFFFFF";
const defaultColor = "#189952";
const taskName = ref('')
let globeStatus:any = 0;
onMounted(() => {
//6
heatList.value = settingStore.heatAreaConfig.map((item:any) => {
@ -176,17 +184,17 @@ onMounted(() => {
tubeBaseConfig();
//socket
const wsClient = createWebSocket(sharedWsUrl);
let globeStatus:any = 0;
const subscription = wsClient.dataOb.subscribe((data) => {
if(!globeStatus){// TODO
console.log('globeStatus====', data)
}
globeStatus = 1;
if (data.type === "cmd") {
console.log('data--首页上报的事件--', data)
const cmdInfo = getTxnRecord(data.data.commandId, "task");
if (cmdInfo) {
const cmdName = CmdDescMap[cmdInfo.command];
const command:any = cmdInfo.command;
//@ts-ignore
const cmdName = CmdDescMap[command];
const result = data.data.success
? "执行完毕"
: `执行失败 ${data.data.message}`;
@ -225,11 +233,20 @@ const onSelectedTray = (heatAreaItem: any, type:string) => {
heatList.value.forEach((item:any) => {
if(item.id == heatAreaItem.id){
item = heatAreaItem
onHeadleCraft(item)
}
})
console.log('selectedTrayList---', selectedTrayList.value)
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;
@ -249,7 +266,6 @@ const onCraftStart = () => {
return;
}
//
let hasCraft = true;
let len = selectedTrayList.value.length;
@ -261,12 +277,21 @@ const onCraftStart = () => {
break;
}
const params = {
craftId: item.craftInfo.id,
craftId:null,
heatId: item.id
}
craftStart(params).then(res => {
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){
@ -280,6 +305,11 @@ const onCraftStart = () => {
}
}
//
const onCraftStop = () => {
}
const tubeBaseConfig = () => {
//16
for (let i = 0; i < 16; i++) {
@ -302,30 +332,21 @@ const onAddLiquid = () => {
}
};
//
const onHandleSelectedCraft = (craftInfo:any) => {
if(selectedTrayList.value.length == 1){
let item = selectedTrayList.value[0]
item.isSelect = true;
item.craftInfo = craftInfo
onSelectedTray(item,'isMove')
changeVisible();
}
}
const onAddSolution = (data: any) => {
let ids = data.map((item: any) => item.id);
//
data.forEach((item:any) => {
onSendCmd('injectFluid', item)
});
tubeList.forEach((item: any) => {
if (ids.includes(item.id)) {
item.default = defaultColor;
item.color = defaultColor;
item.isSelected = true;
}
});
const params = {
injectFluids:data
}
injectFluid(params)
globeStatus = 0
// tubeList.forEach((item: any) => {
// if (ids.includes(item.id)) {
// item.default = defaultColor;
// item.color = defaultColor;
// item.isSelected = true;
// }
// });
};
const changeVisible = () => {
@ -339,14 +360,26 @@ const onChooseCaft = () => {
ElMessage.error("请选择目标加热区");
return;
}
//2
if (selectedTrayList.value.length != 1) {
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

Loading…
Cancel
Save