Browse Source

fix: 异常工艺重构

master
guoapeng 2 months ago
parent
commit
a7e5420146
  1. 2
      src/apis/crafts.ts
  2. 11
      src/components/craft/AddCraft/index.vue
  3. 508
      src/components/home/CheckCraft/index.vue
  4. 26
      src/components/home/Tube/index.vue
  5. 21
      src/layouts/default.vue
  6. 1
      src/stores/systemStore.ts
  7. 1
      src/types/system.d.ts
  8. 78
      src/views/home/index.vue

2
src/apis/crafts.ts

@ -29,6 +29,6 @@ export const craftstatus = (): Promise<CraftTypes.CraftState[]> => http.get(`/mo
export const craftstatusByHeatId = (heatId: string): Promise<CraftTypes.CraftState> => http.get(`/monitor/crafts/status/${heatId}`)
export const craftList = (): Promise<any> => http.get(`/crafts/monitor/list`)
export const craftRestart = (monitorId: string): Promise<CraftTypes.CraftState> => http.post(`/crafts/restart`, { monitorId })
export const craftRestart = (params: any): Promise<CraftTypes.CraftState> => http.post(`/crafts/restart`, params)
export const craftRestore = (isRestore: boolean): Promise<null> => http.get(`/crafts/restore/isRestore=${isRestore}`)
export const craftRemove = (monitorId: string): Promise<CraftTypes.CraftState> => http.delete(`/crafts/remove`, { params: { monitorId } })

11
src/components/craft/AddCraft/index.vue

@ -54,14 +54,17 @@ const okHandle = async () => {
if (step.params.minutes || step.params.seconds) {
step.params.time = (step.params.minutes || 0) * 60 + (step.params.seconds || 0) || undefined
}
else {
step.params.time = undefined
}
if (step.params.temperature > 400 && step.method === 'anneal') {
errorMsg.push(`步骤${index + 1}: 退火温度不能超过400度`)
}
if (step.params.temperature > 200 && step.method === 'heat') {
errorMsg.push(`步骤${index + 1}: 加热温度不能超过400度`)
errorMsg.push(`步骤${index + 1}: 加热温度不能超过200度`)
}
if (step.params.temperature > 200 && step.method === 'dry') {
errorMsg.push(`步骤${index + 1}: 烘干温度不能超过400度`)
errorMsg.push(`步骤${index + 1}: 烘干温度不能超过200度`)
}
}
step.params.description = `${index + 1}.`
@ -70,10 +73,10 @@ const okHandle = async () => {
step.params.description += `针头高度${step.params.height}mm, 加${step.params.volume}ml水清洗${step.params.cycle}`
break
case 'addThin':
step.params.description += `添加稀硝酸-${step.params.volume}ml`
step.params.description += `添加稀硝酸 ${step.params.volume}ml`
break
case 'addThick':
step.params.description += `添加浓硝酸-${step.params.volume}ml`
step.params.description += `添加浓硝酸 ${step.params.volume}ml`
break
case 'reduceLiquid':
step.params.description += `针头高度${step.params.height}mm抽取液体`

508
src/components/home/CheckCraft/index.vue

@ -1,61 +1,402 @@
<script setup lang="ts">
import { craftList, craftRemove, craftRestart } from 'apis/crafts'
import { craftRemove, craftRestart } from 'apis/crafts'
import { ElMessageBox } from 'element-plus'
import { FtMessage } from 'libs/message'
import { onMounted, ref } from 'vue'
import { allPropertiesDefined } from 'libs/utils'
import { cloneDeep } from 'lodash'
import { useHomeStore } from 'stores/homeStore'
import { computed, onMounted, ref } from 'vue'
const emits = defineEmits(['close'])
const props = defineProps({
sourceData: {
type: Object,
default: () => ({
monitorId: 61, // id
heatId: 'heat_module_02', // id
craftsId: 3, // id
craftsName: '仅加热', //
currentStepIndex: 2, //
currentStepResult: '已经加热分钟已经加热分钟已经加热分钟已经加热分钟已经加热分钟已经加热分钟:2', //
steps: [
{
name: '预热',
method: 'preHeat',
params: {
temperature: 12,
description: '1.预热到12度',
},
},
{
name: '加稀硝酸',
method: 'addThin',
params: {
volume: 3,
description: '2.添加稀硝酸 3ml',
},
},
{
name: '加热',
method: 'heat',
params: {
temperature: 4,
time: 60,
description: '3.加热: 4度, 保持1分0秒',
minutes: 1,
},
},
{
name: '加浓硝酸',
method: 'addThick',
params: {
height: 6,
volume: 5,
description: '4.添加浓硝酸 5ml',
},
},
{
name: '清洗',
method: 'clean',
params: {
cycle: 8,
height: 7,
volume: 2,
description: '5.针头高度7mm, 加2ml水清洗8次',
},
},
{
name: '烘干',
method: 'dry',
params: {
temperature: 9,
time: 60,
description: '6.烘干: 9度, 保持1分0秒',
minutes: 1,
},
},
{
name: '退火',
method: 'anneal',
params: {
temperature: 11,
time: 420,
description: '7.退火: 11度, 保持7分0秒',
minutes: 7,
},
},
],
columns: [//
1,
5,
],
}),
},
})
const emits = defineEmits(['ok', 'close'])
const loading = ref(true)
const homeStore = useHomeStore()
onMounted(async () => {
tableData.value = await craftList()
loading.value = false
form.value = {
craftId: props.sourceData.craftsId,
heatId: props.sourceData.heatId,
craftMonitorId: props.sourceData.monitorId,
modifyParam: {
},
columns: cloneDeep(props.sourceData.columns),
steps: cloneDeep(props.sourceData.steps),
}
selectedColumns.value = selectedColumns.value.map((item, index) => {
return props.sourceData.columns.includes(index + 1)
})
})
const tableData = ref<any[]>([])
const form = ref<any>({})
const validateHandle = (rule: any, value: any, callback: any) => {
if (!value?.length) {
callback(new Error('请选择试管'))
}
else {
callback()
}
}
const resumeCraftHandle = async (monitorId: string) => {
await craftRestart(monitorId)
FtMessage.success('恢复成功')
loading.value = true
tableData.value = await craftList()
loading.value = false
const rules = {
columns: [
{ required: true, message: '请选择试管', trigger: 'change', validator: validateHandle },
],
}
const stopCraftHandle = async (monitorId: string) => {
await craftRemove(monitorId)
const hearInfo = computed(() => {
return homeStore.heatAreaList.find(item => item.value === props.sourceData.heatId)
})
const resumeCraftHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
//
const errorMsg: string[] = []
const invalidStepIndex = form.value.steps.findIndex((step: any, index: number) => {
if (['heat', 'dry', 'anneal'].includes(step.method)) {
if (step.params.minutes || step.params.seconds) {
step.params.time = (step.params.minutes || 0) * 60 + (step.params.seconds || 0) || undefined
}
else {
step.params.time = undefined
}
if (step.params.temperature > 400 && step.method === 'anneal') {
errorMsg.push(`步骤${index + 1}: 退火温度不能超过400度`)
}
if (step.params.temperature > 200 && step.method === 'heat') {
errorMsg.push(`步骤${index + 1}: 加热温度不能超过200度`)
}
if (step.params.temperature > 200 && step.method === 'dry') {
errorMsg.push(`步骤${index + 1}: 烘干温度不能超过200度`)
}
}
step.params.description = `${index + 1}.`
switch (step.method) {
case 'clean':
step.params.description += `针头高度${step.params.height}mm, 加${step.params.volume}ml水清洗${step.params.cycle}`
break
case 'addThin':
step.params.description += `添加稀硝酸 ${step.params.volume}ml`
break
case 'addThick':
step.params.description += `添加浓硝酸 ${step.params.volume}ml`
break
case 'reduceLiquid':
step.params.description += `针头高度${step.params.height}mm抽取液体`
break
case 'preHeat':
step.params.description += `预热到${step.params.temperature}`
break
case 'heat':
step.params.description += `加热: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
break
case 'dry':
step.params.description += `烘干: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
break
case 'anneal':
step.params.description += `退火: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
break
}
return !allPropertiesDefined(step.params, ['minutes', 'seconds', 'description'])
})
if (invalidStepIndex !== -1) {
FtMessage.error(`步骤${invalidStepIndex + 1}: 请填写完整参数`)
return
}
if (errorMsg.length) {
FtMessage.error(errorMsg.join('; '))
return
}
form.value.modifyParam = form.value.steps?.[props.sourceData.currentStepIndex]?.params
await craftRestart(form.value)
FtMessage.success('恢复成功')
emits('ok')
}
catch (e) {
console.log(e)
}
}
const stopCraftHandle = async () => {
await ElMessageBox.confirm('确定删除当前工艺?', '消息', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
await craftRemove(props.sourceData.monitorId)
FtMessage.success('删除成功')
loading.value = true
tableData.value = await craftList()
loading.value = false
emits('ok')
}
const cancel = () => {
emits('close')
}
const stepMap = {
addThin: '加稀硝酸',
addThick: '加浓硝酸',
clean: '清洗',
preHeat: '预热',
heat: '加热',
dry: '烘干',
anneal: ' 退火',
}
const formRef = ref()
const selectVisible = ref(false)
const checkChange = () => {
selectedColumns.value = Array.from({ length: 5 }).fill(selectVisible.value)
form.value.columns = selectedColumns.value.map((item, index) => index + 1).filter(item => selectedColumns.value[item - 1])
formRef.value.validateField('columns')
}
const selectedColumns = ref(Array.from({ length: 5 }).fill(false))
const mousedownHandle = async (index: number) => {
selectedColumns.value[index - 1] = !selectedColumns.value[index - 1]
form.value.columns = selectedColumns.value.map((item, index) => {
return item ? index + 1 : false
}).filter(item => item !== false)
formRef.value.validateField('columns')
}
</script>
<template>
<FtDialog visible :loading title="工艺恢复" width="70%" :ok-handle="okHandle" @cancel="cancel">
<el-table :data="tableData">
<el-table-column label="工艺名称" prop="craftsName" />
<el-table-column label="当前执行步骤">
<template #default="{ row }">
<span>{{ row.steps?.[row.currentstepIndex]?.params?.description }}</span>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<ft-button type="primary" :click-handle="() => resumeCraftHandle(row.monitorId)">
恢复
</ft-button>
<ft-button type="danger" :click-handle="() => stopCraftHandle(row.monitorId)">
删除
</ft-button>
</template>
</el-table-column>
</el-table>
<FtDialog visible title="工艺异常" width="80%">
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto" class="form-box" label-position="left">
<el-row>
<el-col :span="10">
<el-form-item label="加热区">
<el-tag>{{ hearInfo?.label }}</el-tag>
</el-form-item>
<el-form-item label="工艺名称">
<el-tooltip :content="sourceData.craftsName" placement="top" trigger="click">
<el-tag>{{ sourceData.craftsName }}</el-tag>
</el-tooltip>
</el-form-item>
<el-form-item label="当前步骤">
<el-tooltip :content="sourceData?.steps?.[sourceData?.currentStepIndex - 1]?.params?.description" placement="top" trigger="click">
<el-tag>{{ sourceData?.steps?.[sourceData?.currentStepIndex - 1]?.params?.description }}</el-tag>
</el-tooltip>
</el-form-item>
<el-form-item label="步骤结果">
<el-tooltip :content="sourceData?.currentStepResult" placement="top" trigger="click">
<el-tag>{{ sourceData?.currentStepResult }}</el-tag>
</el-tooltip>
</el-form-item>
<el-form-item label="选择试管" prop="columns">
<el-checkbox v-model="selectVisible" style="margin-right: 10px" @change="checkChange">
全选
</el-checkbox>
<div class="tube-item">
<div
v-for="item in 5"
:key="item"
class="tube-line"
:class="{ 'tube-line-active': selectedColumns[item - 1] }"
@click.prevent="() => mousedownHandle(item)"
@touch.prevent="() => mousedownHandle(item)"
>
<span v-for="i in 8" :key="i" class="tube-line-inner" />
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="14">
<div v-if="form.steps.length" class="step-box">
<div
v-for="(item, index) in form.steps"
:key="index"
class="step-item"
:class="{ 'step-item-success': sourceData?.currentStepIndex > index, 'step-item-ing': sourceData?.currentStepIndex === index }"
>
<el-form-item :label="sourceData?.currentStepIndex === index ? `${index + 1}: ${stepMap[item.method]}` : ''">
<template v-if="sourceData?.currentStepIndex !== index">
<span>{{ item.params.description }}</span>
</template>
<template v-else>
<template v-if="item.method === 'clean'">
<el-input v-model.number="item.params.height" type="number" size="small" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
<el-input
v-model.number="item.params.volume"
style="width: 100px"
type="number"
size="small"
placeholder="请输入加水量"
>
<template #append>
ml
</template>
</el-input>
<el-input v-model.number="item.params.cycle" type="number" size="small" placeholder="请输入次数">
<template #append>
</template>
</el-input>
</template>
<template v-else-if="['addThin', 'addThick'].includes(item.method)">
<el-input v-model.number="item.params.volume" type="number" size="small" placeholder="请输入容量">
<template #append>
ml
</template>
</el-input>
<el-input v-if="item.method === 'addThick'" v-model.number="item.params.height" type="number" size="small" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
</template>
<template v-else-if="item.method === 'reduceLiquid'">
<el-input v-model.number="item.params.height" type="number" size="small" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
</template>
<template v-else-if="item.method === 'preHeat'">
<el-input
v-model.number="item.params.temperature"
type="number"
size="small"
placeholder="请输入温度"
>
<template #append>
</template>
</el-input>
</template>
<template v-else-if="['heat', 'dry', 'anneal'].includes(item.method)">
<el-input v-model.number="item.params.temperature" type="number" :max="item.method === 'anneal' ? 400 : 200" size="small" placeholder="加热温度">
<template #append>
</template>
</el-input>
<el-select
v-model="item.params.minutes"
style="width: 70px"
clearable
size="small"
placeholder="请选择"
>
<el-option v-for="i in 60" :key="i" :label="i" :value="i" />
</el-select>
<span class="unit-text"></span>
<el-select
v-model="item.params.seconds"
style="width: 70px"
clearable
size="small"
placeholder="请选择"
>
<el-option v-for="i in 60" :key="i" :label="i" :value="i" />
</el-select>
<span class="unit-text"></span>
</template>
</template>
</el-form-item>
</div>
</div>
</el-col>
</el-row>
</el-form>
<template #footer>
<ft-button type="primary" :click-handle="resumeCraftHandle">
恢复工艺
</ft-button>
<ft-button type="danger" :click-handle="stopCraftHandle">
删除工艺
</ft-button>
<ft-button @click="cancel">
关闭
</ft-button>
@ -64,5 +405,98 @@ const cancel = () => {
</template>
<style scoped lang="scss">
.step-item-success {
.el-form-item {
background: rgba(168,225,168,0.4) !important;
}
}
.step-item-ing {
.el-form-item {
background: rgba(25,137,250,0.4) !important;
}
}
.step-item {
.el-form-item {
background: rgba(82, 148, 215, 0.06);
padding: 5px;
margin-bottom: 10px;
:deep(.el-form-item__label) {
height: 25px;
line-height: 25px;
}
:deep(.el-form-item__content) {
width: 100%;
display: flex;
align-items: center;
position: relative;
.el-input,
.el-select {
width: 120px;
margin: 0 5px;
}
.info-box {
width: 80%;
height: 100%;
}
}
}
}
.unit-text {
font-size: 12px;
line-height: 25px;
}
.tube-item {
padding: 5px;
background: #384D5D;
border-radius: 10px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(1, 1fr);
grid-gap: 5px;
position: relative;
.tube-line {
display: flex;
flex-direction: column;
.tube-line-inner {
display: inline-block;
width: 25px;
height: 25px;
border-radius: 50%;
background: #fff;
margin: 2px;
transition: background 0.5s;
}
}
.tube-line-disable {
.tube-line-inner {
background: #C6C6C6;
}
}
.tube-line-active {
.tube-line-inner {
background: #26D574;
}
}
}
.el-form-item {
:deep(.el-form-item__content) {
width: 100%;
display: flex;
align-items: center;
position: relative;
.el-tag {
max-width: 90%;
.el-tag__content {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
</style>

26
src/components/home/Tube/index.vue

@ -1,11 +1,12 @@
<script setup lang="ts">
import { pauseCraft, resumeCraft, stopCraft } from 'apis/crafts'
import { craftList, pauseCraft, resumeCraft, stopCraft } from 'apis/crafts'
import { trayTube } from 'apis/home'
import errorIcon from 'assets/images/error.svg'
import ingIcon from 'assets/images/ing.svg'
import jaw_icon from 'assets/images/jaw.svg'
import successIcon from 'assets/images/success.svg'
import waitIcon from 'assets/images/wait.svg'
import CHeckCraft from 'components/home/CheckCraft/index.vue'
import CountDown from 'components/home/Countdown/Countdown.vue'
import { useHomeStore } from 'stores/homeStore'
import { useSystemStore } from 'stores/systemStore'
@ -26,7 +27,7 @@ const props = withDefaults(defineProps<{ data?: System.HeatArea }>(), {
}),
})
const emits = defineEmits(['selectChange', 'setTemperature'])
const emits = defineEmits(['selectChange', 'setTemperature', 'openCraft'])
const homeStore = useHomeStore()
const systemStore = useSystemStore()
@ -66,6 +67,11 @@ const craftSteps = computed(() => {
return steps ? JSON.parse(steps) : undefined
})
const errCraft = computed(() => {
return systemStore.errCraftList.find(item => item.heatId === props.data.moduleCode)
})
console.log(errCraft.value)
const setTemperature = () => {
emits('setTemperature', props.data.moduleCode)
}
@ -88,6 +94,17 @@ const stopCraftHandle = async () => {
})
}
const craftVisible = ref(false)
const openCraft = () => {
craftVisible.value = true
}
const checkCraft = async () => {
const res = await craftList()
systemStore.errorCraft = res && res.length > 0
systemStore.errCraftList = res
}
defineExpose({
activeTubeBox,
})
@ -185,11 +202,14 @@ defineExpose({
<span :class="{ 'active-footer': hearInfo?.selected }"> {{ data.targetTemperature || '--' }}</span>
<el-icon><Setting /></el-icon>
</div>
<el-icon v-if="errCraft" color="#DF1515" size="30" @click="openCraft">
<WarningFilled />
</el-icon>
<ft-button size="small" :type="hearInfo?.selected ? 'primary' : 'default'" @click="tubeSelect">
选择
</ft-button>
</div>
<CHeckCraft v-if="craftVisible" :source-data="errCraft" @close="craftVisible = false" @ok="craftVisible = false;checkCraft()" />
</div>
</template>

21
src/layouts/default.vue

@ -89,26 +89,7 @@ const checkCraftVisible = ref(false)
const checkCraft = async () => {
const res = await craftList()
systemStore.errorCraft = res && res.length > 0
// if (res && res.length > 0) {
// ElMessageBox.confirm(
// ', ? ',
// '',
// {
// confirmButtonText: '',
// cancelButtonText: '',
// closeOnClickModal: false,
// closeOnPressEscape: false,
// type: 'warning',
// customClass: 'init-message',
// },
// ).then(async () => {
// await craftRestore(true)
// checkCraftVisible.value = true
// }).catch(async () => {
// await craftRestore(false)
// })
// }
systemStore.errCraftList = res
}
// metaisDefault=true isDebug=true,debug

1
src/stores/systemStore.ts

@ -245,6 +245,7 @@ export const useSystemStore = defineStore('system', {
systemUser: {
username: '111',
},
errCraftList: [],
loginForm: {
username: import.meta.env.FT_NODE_ENV !== 'prod' ? 'admin' : '',
password: import.meta.env.FT_NODE_ENV !== 'prod' ? '123456' : '',

1
src/types/system.d.ts

@ -2,6 +2,7 @@ declare namespace System {
interface SystemStore {
systemStatus: SystemStatus
errCraftList: any[]
systemList: Socket.NotificationData[]
streamVisible: boolean
isDebug: boolean

78
src/views/home/index.vue

@ -160,7 +160,13 @@ const moreVisible = ref(false)
<template>
<div class="home-page">
<div class="page-top">
<Tube v-for="(item, index) in systemStore.systemStatus.heatModule" :key="item.moduleCode" :data="item" @select-change="homeStore.selectChange(index)" @set-temperature="setTemperature(item)" />
<Tube
v-for="(item, index) in systemStore.systemStatus.heatModule"
:key="item.moduleCode"
:data="item"
@select-change="homeStore.selectChange(index)"
@set-temperature="setTemperature(item)"
/>
</div>
<div class="button-box">
<el-row style="width: 100%; height: 100%" :gutter="20">
@ -175,15 +181,15 @@ const moreVisible = ref(false)
</ft-button>
</el-card>
<el-card>
<ft-button type="primary" size="large" :click-handle="executeCraftHandle">
<ft-button type="primary" style="margin: auto 0;" class="manual-button" size="large" :click-handle="executeCraftHandle">
执行工艺
</ft-button>
<ft-button type="primary" size="large" :click-handle="() => checkCraftVisible = true">
<el-icon v-if="systemStore.errorCraft" color="#DF1515" size="15" style="margin-right: 5px">
<WarningFilled />
</el-icon>
异常工艺
</ft-button>
<!-- <ft-button type="primary" size="large" :click-handle="() => checkCraftVisible = true"> -->
<!-- <el-icon v-if="systemStore.errorCraft" color="#DF1515" size="15" style="margin-right: 5px"> -->
<!-- <WarningFilled /> -->
<!-- </el-icon> -->
<!-- 异常工艺 -->
<!-- </ft-button> -->
</el-card>
<el-card>
<ft-button type="primary" size="large" :click-handle="trayInHandle">
@ -299,41 +305,41 @@ const moreVisible = ref(false)
<style scoped lang="scss">
.home-page {
.page-top{
height: 65%;
border-radius: 8px;
padding: 0 10px 10px;
display: grid;
grid-template-columns: repeat(4, 1fr); /* 创建3列等宽轨道 */
grid-template-rows: repeat(1, auto); /* 创建2行自动高度 */
gap: 10px;
justify-content: center; /* 水平居中 */
align-items: center;
.page-top{
height: 65%;
border-radius: 8px;
padding: 0 10px 10px;
display: grid;
grid-template-columns: repeat(4, 1fr); /* 创建3列等宽轨道 */
grid-template-rows: repeat(1, auto); /* 创建2行自动高度 */
gap: 10px;
justify-content: center; /* 水平居中 */
align-items: center;
}
.button-box {
height: 35%;
//display: grid;
//grid-template-columns: repeat(6, 1fr); /* 3 */
//grid-template-rows: repeat(4, 1fr); /* 2 */
//gap: 10px 20px;
.ft-button {
//width: 100px;
font-weight: bold;
margin-right: 0;
.my-button {
padding: 0 !important;
border: 3px solid #1989fa !important;
}
.button-box {
height: 35%;
//display: grid;
//grid-template-columns: repeat(6, 1fr); /* 3 */
//grid-template-rows: repeat(4, 1fr); /* 2 */
//gap: 10px 20px;
.ft-button {
//width: 100px;
font-weight: bold;
margin-right: 0;
.my-button {
padding: 0 !important;
border: 3px solid #1989fa !important;
}
}
}
}
}
}
.el-col {
height: 100%;
}
.manual-box {
width: 100%;
width: 100%;
height: 100%;
display: flex;
align-items: center;

Loading…
Cancel
Save