|
|
@ -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> |