|
|
<script setup lang="ts"> import { getContainerList } from 'apis/container' import { createCraft, updateCraft } from 'apis/crafts' import { getSolsList } from 'apis/solution' import emptyIcon from 'assets/images/empty.svg' import { FtMessage } from 'libs/message' import { allPropertiesDefined } from 'libs/utils' import { cloneDeep } from 'lodash' import { onMounted, ref } from 'vue' import { useRoute } from 'vue-router'
const props = defineProps({ sourceData: { type: Object, default: () => ({}), }, })
const emits = defineEmits(['ok', 'cancel'])
const route = useRoute() const oresId: number = route.query.oreId as unknown as number
const containerList = ref<Container.ContainerItem[]>([]) const solutionList = ref<Solution.SolutionItem[]>([])
const loading = ref(true) onMounted(async () => { containerList.value = (await getContainerList())?.filter(item => item.type === 0) solutionList.value = (await getSolsList()).list if (props.sourceData) { form.value = { ...props.sourceData, stepList: JSON.parse(props.sourceData.steps || '[]') } form.value.stepList?.forEach((step: CraftTypes.StepItem) => { if (step.params.second) { step.params.minutes = Math.floor(step.params.second / 60) step.params.seconds = step.params.second % 60 } if (step.params.coolingSecond) { step.params.coolingMinutes = Math.floor(step.params.coolingSecond / 60) step.params.coolingSeconds = step.params.coolingSecond % 60 } }) } loading.value = false })
const form = ref<{ name?: string stepList?: CraftTypes.StepItem[] steps?: string oresId?: number id?: number }>({ stepList: [], }) const formRef = ref()
const rules = { name: [ { required: true, trigger: 'blur', message: '请输入工艺名称' }, ], }
const okHandle = async () => { try { const valid = await formRef.value.validate() if (!valid) { return } // 找到第一个参数不完整的步骤
const invalidStepIndex = form.value.stepList?.findIndex( (step: any, index) => { if (['startHeating', 'shaking'].includes(step.method)) { if (step.params.minutes || step.params.seconds) { step.params.second = (step.params.minutes || 0) * 60 + (step.params.seconds || 0) || undefined } if (step.params.coolingMinutes || step.params.coolingSeconds) { step.params.coolingSecond = (step.params.coolingMinutes || 0) * 60 + (step.params.coolingSeconds || 0) || undefined } } step.params.description = `${index + 1}.` switch (step.method) { case 'addLiquid': step.params.description = step.params.list.map(item => `试管[${item.tubeNums.length === 16 ? '全部' : item.tubeNums.join(',')}]: ${ item.solutionList.map(s => `添加${solutionList.value.find(ss => ss.id === s.solutionId)?.name}- ${s.volume}ml-偏移量${s.offset}ml`).join(';') }`).join(';')
break case 'startHeating': step.params.description = `加热: ${step.params.temperature}度, 保持${step.params.minutes || 0}分${step.params.seconds || 0}秒` break case 'shaking': step.params.description = `摇匀: ${step.params.second}秒` break case 'takePhoto': step.params.description = `拍照` break } return !allPropertiesDefined(step.params, ['minutes', 'seconds', 'description']) },
) console.log(form.value)
if (invalidStepIndex !== -1) { FtMessage.error(`步骤${(invalidStepIndex || 0) + 1}: 请填写完整参数`) return }
form.value.steps = JSON.stringify(form.value.stepList) form.value.oresId = oresId if (form.value.id) { await updateCraft(form.value) } else { await createCraft(form.value) } FtMessage.success('保存成功') emits('ok') } catch (error) { console.log(error) } } const cancel = () => { emits('cancel') }
const stepMap: Record<string, CraftTypes.StepItem> = { // addLiquid: { name: '加液', method: 'addLiquid', params: { addLiquidList: [{ containerId: undefined, volume: undefined }], description: undefined } },
startHeating: { name: '加热', method: 'startHeating', params: { temperature: undefined, second: undefined, description: undefined, minutes: undefined, seconds: undefined } }, shaking: { name: '摇匀', method: 'shaking', params: { second: undefined } }, takePhoto: { name: '拍照', method: 'takePhoto', params: { } }, }
const addStep = (data: CraftTypes.StepItem) => { form.value.stepList?.push(data) }
const addLiquidForm = ref<any>({ tubeNums: [], solutionList: [ { solutionId: undefined, volume: undefined, offset: undefined, }, ], }) const addLiquidRules = { tubeNums: [ { required: true, message: '请选择试管', trigger: 'change' }, ], }
const addLiquidFormRef = ref()
const activeTube = ref(Array.from({ length: 16 }).fill(false))
const selectVisible = ref(false) const checkChange = () => { activeTube.value = Array.from({ length: 16 }).fill(selectVisible.value) addLiquidForm.value.tubeNums = activeTube.value.map((item, index) => index + 1).filter(item => activeTube.value[item - 1]) addLiquidFormRef.value.validateField('tubeNums') }
const mousedownHandle = async (e: Event) => { let event if ('touches' in e) { event = (e.touches as TouchList)[0] } else { event = e } if (event.target!.classList!.contains('tube-inner')) { const num = event.target!.getAttribute('index') activeTube.value[Number(num) - 1] = !activeTube.value[Number(num) - 1] addLiquidForm.value.tubeNums = activeTube.value.map((item, index) => index + 1).filter(item => activeTube.value[item - 1]) } }
const addHandle = async () => { try { const valid = await addLiquidFormRef.value.validate() if (!valid) { return } console.log(addLiquidForm.value) // addList.value!.push(addLiquidForm)
const index = form.value.stepList?.findIndex(item => item.method === 'addLiquid') if (index !== -1) { form.value.stepList?.[index || 0].params?.list?.push(cloneDeep(addLiquidForm.value)) } else { form.value.stepList?.push({ name: '加液', method: 'addLiquid', params: { list: [cloneDeep(addLiquidForm.value)] }, } as CraftTypes.StepItem) } addLiquidForm.value = { tubeNums: [], solutionList: [ { solutionId: undefined, volume: undefined, offset: undefined, }, ], } activeTube.value = Array.from({ length: 16 }).fill(false) selectVisible.value = false addLiquidFormRef.value.resetFields() } catch (error) { console.log(error) } } </script>
<template> <FtDialog visible :loading :title="form.id ? '编辑工艺' : '新增工艺'" width="90%" :ok-handle="okHandle" @cancel="cancel"> <el-form ref="formRef" label-width="auto" :model="form" :rules="rules" class="form-box"> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="工艺名称" prop="name"> <el-input v-model="form.name" placeholder="请输入工艺名称" /> </el-form-item> <el-form-item label="步骤列表"> <div class="button-content"> <el-tag v-for="item in stepMap" :key="item" size="large" @click="() => addStep(item)"> <div style="display: flex;align-items: center;justify-content: space-around;width: 100%;"> <el-icon><Plus /></el-icon> <span> {{ item.name }}</span> </div> </el-tag> </div> <el-form ref="addLiquidFormRef" :model="addLiquidForm" :rules="addLiquidRules" label-width="auto" class="liquid-box"> <p>加液</p> <el-form-item label="" prop="tubeNums"> <el-checkbox v-model="selectVisible" style="margin-right: 10px" @change="checkChange"> 全选 </el-checkbox> <div class="tube-item" @click.prevent="mousedownHandle" @touch.prevent="mousedownHandle"> <span v-for="item in 16" :key="item" class="tube-inner" :class="{ 'tube-inner-active': activeTube[item - 1] }" :index="item" /> </div> </el-form-item> <el-row> <el-col :span="7" style="text-align: center"> 溶液 </el-col> <el-col :span="7" style="text-align: center"> 容量 </el-col> <el-col :span="7" style="text-align: center"> 偏移量 </el-col> </el-row> <div class="solution-list"> <el-row v-for="(s, index) in addLiquidForm.solutionList" :key="index" :gutter="5" class="solution-item"> <el-col :span="7"> <el-form-item label="" :prop="`solutionList.${index}.solutionId`" :rules="{ required: true, message: '请选择溶液', trigger: 'change', }" > <el-select v-model="s.solutionId" size="small" style="width: 100%" placeholder="溶液"> <el-option v-for="item in solutionList" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </el-form-item> </el-col> <el-col :span="7"> <el-form-item label="" :prop="`solutionList.${index}.volume`" :rules="{ required: true, message: '请输入容量', trigger: 'blur', }" > <el-input v-model.number="s.volume" size="small" type="number" style="width: 100%" placeholder="容量"> <template #append> ml </template> </el-input> </el-form-item> </el-col> <el-col :span="7"> <el-form-item label="" :prop="`solutionList.${index}.offset`" :rules="{ required: true, message: '请输入偏移量', trigger: 'blur', }" > <el-input v-model.number="s.offset" size="small" type="number" style="width: 100%" placeholder="偏移量"> <template #append> ml </template> </el-input> </el-form-item> </el-col> <el-col :span="3" class="icon-box"> <el-icon v-if="addLiquidForm.solutionList.length > 1" color="red" @click="addLiquidForm.solutionList.splice(index, 1)"> <RemoveFilled /> </el-icon> <el-icon v-if="index === addLiquidForm.solutionList.length - 1" color="green" @click="addLiquidForm.solutionList.push({ solutionId: undefined, volume: undefined, offset: undefined })" > <CirclePlusFilled /> </el-icon> </el-col> </el-row> </div>
<el-form-item> <div style="width: 100%;display: flex;justify-content: center"> <ft-button type="primary" @click="addHandle"> 添加 </ft-button> </div> </el-form-item> </el-form> </el-form-item> </el-col> <el-col :span="12"> <div v-if="form.stepList?.length" class="step-box"> <div v-for="(item, index) in form.stepList" :key="index" class="step-item"> <el-form-item :label="`${index + 1}: ${item.name}`"> <div v-if="item.method === 'addLiquid'" class="list-box"> <div v-for="(liquid, liquidIndex) in item.params?.list || []" :key="liquidIndex"> <span>试管: {{ liquid.tubeNums.length === 16 ? '全部' : liquid.tubeNums.join(',') }} </span> <div v-for="(s, sIndex) in liquid.solutionList" :key="sIndex"> <span>添加</span> <span>{{ solutionList.find(solution => solution.id === s.solutionId)?.name }}</span> <span>{{ s.volume }}</span> <span>ml-</span> <span>偏移量</span> <span>{{ s.offset }}</span> <span>ml</span> </div> </div> </div> <div v-else-if="['startHeating'].includes(item.method)"> <el-input v-model.number="item.params.temperature" type="number" size="small" placeholder="加热温度"> <template #append> ℃ </template> </el-input> <br> <div> <span>保持时间</span> <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> </div> <div> <span>冷却时间</span> <el-select v-model="item.params.coolingMinutes" 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.coolingSeconds" 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> </div> </div> <template v-else-if="['shaking'].includes(item.method)"> <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> <el-icon style="margin-left: auto;" @click="() => form.stepList?.splice(index, 1)"> <Close /> </el-icon> </el-form-item> </div> </div> <div v-else class="empty-box"> <img :src="emptyIcon" alt=""> <span>暂无步骤</span> </div> </el-col> </el-row> </el-form> </FtDialog> </template>
<style scoped lang="scss"> .form-box { height: 70vh; .el-row { height: 100%; .el-col:first-child { border-right: 1px solid #eee; } .el-col { height: 100%; .step-box { height: 100%; overflow: auto; } } } } .button-content { width: 100%; display: grid; grid-template-columns: repeat(2, 1fr); /* 创建3列等宽轨道 */ grid-template-rows: repeat(2, auto); /* 创建2行自动高度 */ gap: 10px; margin-bottom: 10px; } :deep(.el-tag__content) { width: 100%; } .empty-box { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #ccc; img { margin-bottom: 10px; } } .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;
.el-input, .el-select { width: 120px; margin: 0 5px; } .list-box { width: 90%; height: 100%; .list-item { display: flex; align-items: center; padding-bottom: 10px; .el-icon { cursor: pointer; font-size: 20px; margin-left: 10px; } } } } } } .unit-text { font-size: 12px; line-height: 25px; }
.tube-item { padding: 5px; background: #384D5D; border-radius: 10px; display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(4, 1fr); grid-gap: 5px; position: relative; .tube-disable { position: absolute; width: 100%; height: 100%; top: 0; left: 0; background: rgba(255,255,255,0.9); border-radius: 9px; } .tube-inner { display: inline-block; width: 25px; height: 25px; border-radius: 50%; background: #fff; margin: 2px; transition: background 0.5s; } .tube-inner-active { background: #26D574; } }
.liquid-box { background: #ecf5ff; padding: 0 10px; p { text-align: center; color: #1989fa; } } .solution-list { max-height: 100px; overflow: hidden; overflow-y: auto; } .solution-item { margin-bottom: 10px;
} .el-form-item .el-form-item { margin-bottom: 10px; } .icon-box { display: flex; margin-top: 4px; justify-content: space-between; .el-icon { font-size: 18px; line-height: 100%; } } </style>
|