Browse Source

fix: 工艺重构

master
guoapeng 2 months ago
parent
commit
2252a59be5
  1. 8
      src/components/common/FTDialog/index.vue
  2. 290
      src/components/craft/AddCraft/index.vue
  3. 11
      src/libs/utils.ts
  4. 19
      src/types/craft.d.ts
  5. 59
      src/views/craft/index.vue

8
src/components/common/FTDialog/index.vue

@ -18,6 +18,10 @@ const props = defineProps({
type: Function, type: Function,
default: () => {}, default: () => {},
}, },
loading: {
type: Boolean,
default: false,
},
}) })
const emits = defineEmits(['update:visible', 'ok', 'cancel']) const emits = defineEmits(['update:visible', 'ok', 'cancel'])
@ -54,7 +58,9 @@ watch(
:width="width" :width="width"
:before-close="cancel" :before-close="cancel"
> >
<slot />
<div v-loading="loading">
<slot />
</div>
<template #footer> <template #footer>
<div v-if="$slots.footer" class="dialog-footer"> <div v-if="$slots.footer" class="dialog-footer">
<slot name="footer" /> <slot name="footer" />

290
src/components/craft/AddCraft/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>

11
src/libs/utils.ts

@ -112,3 +112,14 @@ export function formatDateTime(template: string = 'YYYY-MM-DD HH:mm:ss', now: Da
return template.replace(/YYYY|MM|DD|HH|mm|ss/g, token => tokens[token]!) return template.replace(/YYYY|MM|DD|HH|mm|ss/g, token => tokens[token]!)
} }
export function allPropertiesDefined(obj: Record<string, any>, excludeKeys: string[] = []): boolean {
return Object.entries(obj).every(([key, value]) => {
if (key === 'addLiquidList') {
return value.every((item: any) => allPropertiesDefined(item))
}
else {
return excludeKeys.includes(key) ? true : value
}
})
}

19
src/types/craft.d.ts

@ -95,4 +95,23 @@ declare namespace CraftTypes {
state: string state: string
steps: StepStruct[] steps: StepStruct[]
} }
interface PreHeatParams { temperature?: number, description?: string }
interface AddLiquidParams { addLiquidList?: Array<{ containerId: number, volume: number }>, description?: string }
interface HeatParams { temperature?: number, second?: number, description?: string, minutes?: number, seconds?: number }
interface DryParams { second?: number, description?: string }
interface AnnealParams {}
type StepParam =
| { method: 'preHeat', params: PreHeatParams }
| { method: 'addLiquid', params: AddLiquidParams }
| { method: 'startHeating', params: HeatParams }
| { method: 'shaking', params: DryParams }
| { method: 'takePhoto', params: AnnealParams }
interface StepItem {
name: string
method: StepParam['method']
params: StepParam['params']
}
} }

59
src/views/craft/index.vue

@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { delCraft, getCraftList, setCraft, startCraft } from '@/apis/crafts' import { delCraft, getCraftList, setCraft, startCraft } from '@/apis/crafts'
import AddCraft from '@/components/craft/AddCraft/index.vue'
import AddCraftDialog from '@/components/craft/AddCraftDialog.vue' import AddCraftDialog from '@/components/craft/AddCraftDialog.vue'
import CraftStatus from '@/components/craft/CraftStatus.vue' import CraftStatus from '@/components/craft/CraftStatus.vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
@ -9,7 +10,7 @@ import { useRoute, useRouter } from 'vue-router'
const tableData = ref<CraftTypes.Craft[]>([]) const tableData = ref<CraftTypes.Craft[]>([])
const addCraftRef = ref<CraftTypes.AddCraftType | null>(null) const addCraftRef = ref<CraftTypes.AddCraftType | null>(null)
const selectable = ref() const selectable = ref()
const multipleSelection = ref<CraftTypes.Craft[]>()
const multipleSelection = ref<CraftTypes.Craft[]>([])
const heatVisible = ref(false) const heatVisible = ref(false)
const heatId = ref() const heatId = ref()
const craftStatusRef = ref<CraftTypes.craftStatus | null>(null) const craftStatusRef = ref<CraftTypes.craftStatus | null>(null)
@ -50,20 +51,17 @@ const queryCrafList = () => {
}) })
} }
const sourceData = ref<CraftTypes.Craft | null>(null)
const addCraftVisible = ref(false)
const onAddCraft = () => { const onAddCraft = () => {
if (addCraftRef.value) {
addCraftRef.value.openDialog()
}
sourceData.value = null
addCraftVisible.value = true
} }
const onEditCraft = () => { const onEditCraft = () => {
if (multipleSelection.value && multipleSelection.value.length !== 1) {
ElMessage.warning('请选择一条数据进行编辑')
return
}
if (addCraftRef.value && multipleSelection.value) {
addCraftRef.value.editDialog(multipleSelection.value[0])
}
sourceData.value = multipleSelection.value[0]
addCraftVisible.value = true
} }
const handleSelectionChange = (rows: CraftTypes.Craft[]) => { const handleSelectionChange = (rows: CraftTypes.Craft[]) => {
@ -97,17 +95,17 @@ const onDelCraft = () => {
}) })
} }
const onSelectHeatId = () => {
if (!multipleSelection.value || multipleSelection.value.length !== 1) {
ElMessage.warning('每次只可执行一次工艺')
return
}
heatVisible.value = true
}
const onShowState = () => {
craftStatusRef.value?.showDialog()
}
// const onSelectHeatId = () => {
// if (!multipleSelection.value || multipleSelection.value.length !== 1) {
// ElMessage.warning('')
// return
// }
// heatVisible.value = true
// }
//
// const onShowState = () => {
// craftStatusRef.value?.showDialog()
// }
// //
const onStart = () => { const onStart = () => {
@ -148,18 +146,18 @@ const returnOre = () => {
<FtButton type="primary" @click="onAddCraft"> <FtButton type="primary" @click="onAddCraft">
添加工艺 添加工艺
</FtButton> </FtButton>
<FtButton type="primary" @click="onEditCraft">
<FtButton type="primary" :disabled="multipleSelection.length !== 1" @click="onEditCraft">
编辑工艺 编辑工艺
</FtButton> </FtButton>
<FtButton @click="onDelCraft">
<FtButton :disabled="!multipleSelection.length" @click="onDelCraft">
删除工艺 删除工艺
</FtButton> </FtButton>
<FtButton @click="onSelectHeatId">
执行工艺
</FtButton>
<FtButton @click="onShowState">
查看工艺步骤
</FtButton>
<!-- <FtButton @click="onSelectHeatId"> -->
<!-- 执行工艺 -->
<!-- </FtButton> -->
<!-- <FtButton @click="onShowState"> -->
<!-- 查看工艺步骤 -->
<!-- </FtButton> -->
</section> </section>
<main class="craft-main"> <main class="craft-main">
<el-table :data="tableData" style="width: 100%" size="small" @selection-change="handleSelectionChange"> <el-table :data="tableData" style="width: 100%" size="small" @selection-change="handleSelectionChange">
@ -175,6 +173,7 @@ const returnOre = () => {
<el-table-column prop="updateTime" label="更新日期" /> <el-table-column prop="updateTime" label="更新日期" />
</el-table> </el-table>
</main> </main>
<AddCraft v-if="addCraftVisible" :source-data @ok="addCraftVisible = false;queryCrafList()" @cancel="addCraftVisible = false" />
<AddCraftDialog ref="addCraftRef" @ok="queryCrafList" /> <AddCraftDialog ref="addCraftRef" @ok="queryCrafList" />
<!-- 执行工艺选择加热区 --> <!-- 执行工艺选择加热区 -->
<FtDialog v-model="heatVisible" title="添加矿石" width="30%" :ok-handle="onStart" @cancel="heatVisible = false"> <FtDialog v-model="heatVisible" title="添加矿石" width="30%" :ok-handle="onStart" @cancel="heatVisible = false">

Loading…
Cancel
Save