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.

502 lines
16 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <script setup lang="ts">
  2. import { craftRemove, craftRestart } from 'apis/crafts'
  3. import { ElMessageBox } from 'element-plus'
  4. import { FtMessage } from 'libs/message'
  5. import { allPropertiesDefined } from 'libs/utils'
  6. import { cloneDeep } from 'lodash'
  7. import { useHomeStore } from 'stores/homeStore'
  8. import { computed, onMounted, ref } from 'vue'
  9. const props = defineProps({
  10. sourceData: {
  11. type: Object,
  12. default: () => ({
  13. monitorId: 61, // 异常工艺id
  14. heatId: 'heat_module_02', // 加热区id
  15. craftsId: 3, // 工艺id
  16. craftsName: '仅加热', // 工艺名称
  17. currentStepIndex: 2, // 当前步骤
  18. currentStepResult: '已经加热分钟已经加热分钟已经加热分钟已经加热分钟已经加热分钟已经加热分钟:2', // 当前步骤结果
  19. steps: [
  20. {
  21. name: '预热',
  22. method: 'preHeat',
  23. params: {
  24. temperature: 12,
  25. description: '1.预热到12度',
  26. },
  27. },
  28. {
  29. name: '加稀硝酸',
  30. method: 'addThin',
  31. params: {
  32. volume: 3,
  33. description: '2.添加稀硝酸 3ml',
  34. },
  35. },
  36. {
  37. name: '加热',
  38. method: 'heat',
  39. params: {
  40. temperature: 4,
  41. time: 60,
  42. description: '3.加热: 4度, 保持1分0秒',
  43. minutes: 1,
  44. },
  45. },
  46. {
  47. name: '加浓硝酸',
  48. method: 'addThick',
  49. params: {
  50. height: 6,
  51. volume: 5,
  52. description: '4.添加浓硝酸 5ml',
  53. },
  54. },
  55. {
  56. name: '清洗',
  57. method: 'clean',
  58. params: {
  59. cycle: 8,
  60. height: 7,
  61. volume: 2,
  62. description: '5.针头高度7mm, 加2ml水清洗8次',
  63. },
  64. },
  65. {
  66. name: '烘干',
  67. method: 'dry',
  68. params: {
  69. temperature: 9,
  70. time: 60,
  71. description: '6.烘干: 9度, 保持1分0秒',
  72. minutes: 1,
  73. },
  74. },
  75. {
  76. name: '退火',
  77. method: 'anneal',
  78. params: {
  79. temperature: 11,
  80. time: 420,
  81. description: '7.退火: 11度, 保持7分0秒',
  82. minutes: 7,
  83. },
  84. },
  85. ],
  86. columns: [// 即工艺最原始选择的试管列
  87. 1,
  88. 5,
  89. ],
  90. }),
  91. },
  92. })
  93. const emits = defineEmits(['ok', 'close'])
  94. const homeStore = useHomeStore()
  95. onMounted(async () => {
  96. form.value = {
  97. craftId: props.sourceData.craftsId,
  98. heatId: props.sourceData.heatId,
  99. craftMonitorId: props.sourceData.monitorId,
  100. modifyParam: {
  101. },
  102. columns: cloneDeep(props.sourceData.columns),
  103. steps: cloneDeep(props.sourceData.steps),
  104. }
  105. selectedColumns.value = selectedColumns.value.map((item, index) => {
  106. return props.sourceData.columns.includes(index + 1)
  107. })
  108. })
  109. const form = ref<any>({})
  110. const validateHandle = (rule: any, value: any, callback: any) => {
  111. if (!value?.length) {
  112. callback(new Error('请选择试管'))
  113. }
  114. else {
  115. callback()
  116. }
  117. }
  118. const rules = {
  119. columns: [
  120. { required: true, message: '请选择试管', trigger: 'change', validator: validateHandle },
  121. ],
  122. }
  123. const hearInfo = computed(() => {
  124. return homeStore.heatAreaList.find(item => item.value === props.sourceData.heatId)
  125. })
  126. const resumeCraftHandle = async () => {
  127. try {
  128. const valid = await formRef.value.validate()
  129. if (!valid) {
  130. return
  131. }
  132. // 找到第一个参数不完整的步骤
  133. const errorMsg: string[] = []
  134. const invalidStepIndex = form.value.steps.findIndex((step: any, index: number) => {
  135. if (['heat', 'dry', 'anneal'].includes(step.method)) {
  136. if (step.params.minutes || step.params.seconds) {
  137. step.params.time = (step.params.minutes || 0) * 60 + (step.params.seconds || 0) || undefined
  138. }
  139. else {
  140. step.params.time = undefined
  141. }
  142. if (step.params.temperature > 400 && step.method === 'anneal') {
  143. errorMsg.push(`步骤${index + 1}: 退火温度不能超过400度`)
  144. }
  145. if (step.params.temperature > 200 && step.method === 'heat') {
  146. errorMsg.push(`步骤${index + 1}: 加热温度不能超过200度`)
  147. }
  148. if (step.params.temperature > 200 && step.method === 'dry') {
  149. errorMsg.push(`步骤${index + 1}: 烘干温度不能超过200度`)
  150. }
  151. }
  152. step.params.description = `${index + 1}.`
  153. switch (step.method) {
  154. case 'clean':
  155. step.params.description += `针头高度${step.params.height}mm, 加${step.params.volume}ml水清洗${step.params.cycle}`
  156. break
  157. case 'addThin':
  158. step.params.description += `添加稀硝酸 ${step.params.volume}mL`
  159. break
  160. case 'addThick':
  161. step.params.description += `添加浓硝酸 ${step.params.volume}mL`
  162. break
  163. case 'reduceLiquid':
  164. step.params.description += `针头高度${step.params.height}mm抽取液体`
  165. break
  166. case 'preHeat':
  167. step.params.description += `预热到${step.params.temperature}`
  168. break
  169. case 'heat':
  170. step.params.description += `加热: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
  171. break
  172. case 'dry':
  173. step.params.description += `烘干: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
  174. break
  175. case 'anneal':
  176. step.params.description += `退火: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
  177. break
  178. }
  179. return !allPropertiesDefined(step.params, ['minutes', 'seconds', 'description'])
  180. })
  181. if (invalidStepIndex !== -1) {
  182. FtMessage.error(`步骤${invalidStepIndex + 1}: 请填写完整参数`)
  183. return
  184. }
  185. if (errorMsg.length) {
  186. FtMessage.error(errorMsg.join('; '))
  187. return
  188. }
  189. form.value.modifyParam = form.value.steps?.[props.sourceData.currentStepIndex]?.params
  190. await craftRestart(form.value)
  191. FtMessage.success('恢复成功')
  192. emits('ok')
  193. }
  194. catch (e) {
  195. console.log(e)
  196. }
  197. }
  198. const stopCraftHandle = async () => {
  199. await ElMessageBox.confirm('确定删除当前工艺?', '消息', {
  200. confirmButtonText: '确认',
  201. cancelButtonText: '取消',
  202. type: 'warning',
  203. })
  204. await craftRemove(props.sourceData.monitorId)
  205. FtMessage.success('删除成功')
  206. emits('ok')
  207. }
  208. const cancel = () => {
  209. emits('close')
  210. }
  211. const stepMap = {
  212. addThin: '加稀硝酸',
  213. addThick: '加浓硝酸',
  214. clean: '清洗',
  215. preHeat: '预热',
  216. heat: '加热',
  217. dry: '烘干',
  218. anneal: ' 退火',
  219. }
  220. const formRef = ref()
  221. const selectVisible = ref(false)
  222. const checkChange = () => {
  223. selectedColumns.value = Array.from({ length: 5 }).fill(selectVisible.value)
  224. form.value.columns = selectedColumns.value.map((item, index) => index + 1).filter(item => selectedColumns.value[item - 1])
  225. formRef.value.validateField('columns')
  226. }
  227. const selectedColumns = ref(Array.from({ length: 5 }).fill(false))
  228. const mousedownHandle = async (index: number) => {
  229. selectedColumns.value[index - 1] = !selectedColumns.value[index - 1]
  230. form.value.columns = selectedColumns.value.map((item, index) => {
  231. return item ? index + 1 : false
  232. }).filter(item => item !== false)
  233. formRef.value.validateField('columns')
  234. }
  235. </script>
  236. <template>
  237. <FtDialog visible title="工艺异常" width="80%">
  238. <el-form ref="formRef" :model="form" :rules="rules" label-width="auto" class="form-box" label-position="left">
  239. <el-row>
  240. <el-col :span="10">
  241. <el-form-item label="加热区">
  242. <el-tag>{{ hearInfo?.label }}</el-tag>
  243. </el-form-item>
  244. <el-form-item label="工艺名称">
  245. <el-tooltip :content="sourceData.craftsName" placement="top" trigger="click">
  246. <el-tag>{{ sourceData.craftsName }}</el-tag>
  247. </el-tooltip>
  248. </el-form-item>
  249. <el-form-item label="当前步骤">
  250. <el-tooltip :content="sourceData?.steps?.[sourceData?.currentStepIndex - 1]?.params?.description" placement="top" trigger="click">
  251. <el-tag>{{ sourceData?.steps?.[sourceData?.currentStepIndex - 1]?.params?.description }}</el-tag>
  252. </el-tooltip>
  253. </el-form-item>
  254. <el-form-item label="步骤结果">
  255. <el-tooltip :content="sourceData?.currentStepResult" placement="top" trigger="click">
  256. <el-tag>{{ sourceData?.currentStepResult }}</el-tag>
  257. </el-tooltip>
  258. </el-form-item>
  259. <el-form-item label="选择试管" prop="columns">
  260. <el-checkbox v-model="selectVisible" style="margin-right: 10px" @change="checkChange">
  261. 全选
  262. </el-checkbox>
  263. <div class="tube-item">
  264. <div
  265. v-for="item in 5"
  266. :key="item"
  267. class="tube-line"
  268. :class="{ 'tube-line-active': selectedColumns[item - 1] }"
  269. @click.prevent="() => mousedownHandle(item)"
  270. @touch.prevent="() => mousedownHandle(item)"
  271. >
  272. <span v-for="i in 8" :key="i" class="tube-line-inner" />
  273. </div>
  274. </div>
  275. </el-form-item>
  276. </el-col>
  277. <el-col :span="14">
  278. <div v-if="form.steps.length" class="step-box">
  279. <div
  280. v-for="(item, index) in form.steps"
  281. :key="index"
  282. class="step-item"
  283. :class="{ 'step-item-success': sourceData?.currentStepIndex > index, 'step-item-ing': sourceData?.currentStepIndex === index }"
  284. >
  285. <el-form-item :label="sourceData?.currentStepIndex === index ? `${index + 1}: ${stepMap[item.method]}` : ''">
  286. <template v-if="sourceData?.currentStepIndex !== index">
  287. <span>{{ item.params.description }}</span>
  288. </template>
  289. <template v-else>
  290. <template v-if="item.method === 'clean'">
  291. <el-input v-model="item.params.height" type="number" size="small" placeholder="请输入高度">
  292. <template #append>
  293. mm
  294. </template>
  295. </el-input>
  296. <el-input
  297. v-model="item.params.volume"
  298. style="width: 100px"
  299. type="number"
  300. size="small"
  301. placeholder="请输入加水量"
  302. >
  303. <template #append>
  304. mL
  305. </template>
  306. </el-input>
  307. <el-input v-model="item.params.cycle" type="number" size="small" placeholder="请输入次数">
  308. <template #append>
  309. </template>
  310. </el-input>
  311. </template>
  312. <template v-else-if="['addThin', 'addThick'].includes(item.method)">
  313. <el-input v-model="item.params.volume" type="number" size="small" placeholder="请输入容量">
  314. <template #append>
  315. mL
  316. </template>
  317. </el-input>
  318. <el-input v-if="item.method === 'addThick'" v-model="item.params.height" type="number" size="small" placeholder="请输入高度">
  319. <template #append>
  320. mm
  321. </template>
  322. </el-input>
  323. </template>
  324. <template v-else-if="item.method === 'reduceLiquid'">
  325. <el-input v-model="item.params.height" type="number" size="small" placeholder="请输入高度">
  326. <template #append>
  327. mm
  328. </template>
  329. </el-input>
  330. </template>
  331. <template v-else-if="item.method === 'preHeat'">
  332. <el-input
  333. v-model="item.params.temperature"
  334. type="number"
  335. size="small"
  336. placeholder="请输入温度"
  337. >
  338. <template #append>
  339. </template>
  340. </el-input>
  341. </template>
  342. <template v-else-if="['heat', 'dry', 'anneal'].includes(item.method)">
  343. <el-input v-model="item.params.temperature" type="number" :max="item.method === 'anneal' ? 400 : 200" size="small" placeholder="加热温度">
  344. <template #append>
  345. </template>
  346. </el-input>
  347. <el-select
  348. v-model="item.params.minutes"
  349. style="width: 70px"
  350. clearable
  351. size="small"
  352. placeholder="请选择"
  353. >
  354. <el-option v-for="i in 60" :key="i" :label="i" :value="i" />
  355. </el-select>
  356. <span class="unit-text"></span>
  357. <el-select
  358. v-model="item.params.seconds"
  359. style="width: 70px"
  360. clearable
  361. size="small"
  362. placeholder="请选择"
  363. >
  364. <el-option v-for="i in 60" :key="i" :label="i" :value="i" />
  365. </el-select>
  366. <span class="unit-text"></span>
  367. </template>
  368. </template>
  369. </el-form-item>
  370. </div>
  371. </div>
  372. </el-col>
  373. </el-row>
  374. </el-form>
  375. <template #footer>
  376. <ft-button type="primary" :click-handle="resumeCraftHandle">
  377. 恢复工艺
  378. </ft-button>
  379. <ft-button type="danger" :click-handle="stopCraftHandle">
  380. 删除工艺
  381. </ft-button>
  382. <ft-button @click="cancel">
  383. 关闭
  384. </ft-button>
  385. </template>
  386. </FtDialog>
  387. </template>
  388. <style scoped lang="scss">
  389. .step-item-success {
  390. .el-form-item {
  391. background: rgba(168,225,168,0.4) !important;
  392. }
  393. }
  394. .step-item-ing {
  395. .el-form-item {
  396. background: rgba(25,137,250,0.4) !important;
  397. }
  398. }
  399. .step-item {
  400. .el-form-item {
  401. background: rgba(82, 148, 215, 0.06);
  402. padding: 5px;
  403. margin-bottom: 10px;
  404. :deep(.el-form-item__label) {
  405. height: 25px;
  406. line-height: 25px;
  407. }
  408. :deep(.el-form-item__content) {
  409. width: 100%;
  410. display: flex;
  411. align-items: center;
  412. position: relative;
  413. .el-input,
  414. .el-select {
  415. width: 120px;
  416. margin: 0 5px;
  417. }
  418. .info-box {
  419. width: 80%;
  420. height: 100%;
  421. }
  422. }
  423. }
  424. }
  425. .unit-text {
  426. font-size: 12px;
  427. line-height: 25px;
  428. }
  429. .tube-item {
  430. padding: 5px;
  431. background: #384D5D;
  432. border-radius: 10px;
  433. display: grid;
  434. grid-template-columns: repeat(5, 1fr);
  435. grid-template-rows: repeat(1, 1fr);
  436. grid-gap: 5px;
  437. position: relative;
  438. .tube-line {
  439. display: flex;
  440. flex-direction: column;
  441. .tube-line-inner {
  442. display: inline-block;
  443. width: 25px;
  444. height: 25px;
  445. border-radius: 50%;
  446. background: #fff;
  447. margin: 2px;
  448. transition: background 0.5s;
  449. }
  450. }
  451. .tube-line-disable {
  452. .tube-line-inner {
  453. background: #C6C6C6;
  454. }
  455. }
  456. .tube-line-active {
  457. .tube-line-inner {
  458. background: #26D574;
  459. }
  460. }
  461. }
  462. .el-form-item {
  463. :deep(.el-form-item__content) {
  464. width: 100%;
  465. display: flex;
  466. align-items: center;
  467. position: relative;
  468. .el-tag {
  469. max-width: 90%;
  470. .el-tag__content {
  471. width: 100%;
  472. white-space: nowrap;
  473. overflow: hidden;
  474. text-overflow: ellipsis;
  475. }
  476. }
  477. }
  478. }
  479. </style>