5 changed files with 356 additions and 31 deletions
-
6src/components/common/FTDialog/index.vue
-
290src/components/craft/AddCraft/index.vue
-
11src/libs/utils.ts
-
19src/types/craft.d.ts
-
59src/views/craft/index.vue
@ -0,0 +1,290 @@ |
|||||
|
<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 { 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 |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
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) => { |
||||
|
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 |
||||
|
} |
||||
|
} |
||||
|
switch (step.method) { |
||||
|
case 'addLiquid': |
||||
|
step.params.description = step.params.addLiquidList.map(liquid => `添加${solutionList.value.find(s => s.id === containerList.value.find(c => c.id === liquid.containerId)?.solutionId)?.name}-${liquid.volume}ml; `) |
||||
|
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) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<FtDialog visible :loading :title="form.id ? '编辑工艺' : '新增工艺'" width="80%" :ok-handle="okHandle" @cancel="cancel"> |
||||
|
<el-form ref="formRef" label-width="auto" :model="form" :rules="rules" class="form-box"> |
||||
|
<el-row :gutter="30"> |
||||
|
<el-col :span="10"> |
||||
|
<el-form-item label="工艺名称" prop="name"> |
||||
|
<el-input v-model.number="form.name" type="number" 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-item> |
||||
|
</el-col> |
||||
|
<el-col :span="14"> |
||||
|
<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.addLiquidList" :key="liquidIndex" class="list-item"> |
||||
|
<el-select v-model="liquid.containerId" size="small" placeholder="请选择溶液"> |
||||
|
<el-option v-for="c in containerList" :key="c.id" :label="solutionList.find(s => s.id === c.solutionId)?.name" :value="c.id" /> |
||||
|
</el-select> |
||||
|
<el-input v-model.number="liquid.volume" type="number" size="small" placeholder="请输入容量"> |
||||
|
<template #append> |
||||
|
ml |
||||
|
</template> |
||||
|
</el-input> |
||||
|
|
||||
|
<el-icon color="red" @click="item.params.addLiquidList.splice(liquidIndex, 1)"> |
||||
|
<RemoveFilled /> |
||||
|
</el-icon> |
||||
|
<el-icon v-if="liquidIndex === item.params.addLiquidList.length - 1" color="green" @click="item.params.addLiquidList.push({ containerId: undefined, volume: undefined })"> |
||||
|
<CirclePlusFilled /> |
||||
|
</el-icon> |
||||
|
</div> |
||||
|
</div> |
||||
|
<template 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> |
||||
|
<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 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: 50vh; |
||||
|
.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(4, auto); /* 创建2行自动高度 */ |
||||
|
gap: 20px; |
||||
|
} |
||||
|
: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; |
||||
|
} |
||||
|
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue