提交
This commit is contained in:
1 parent
7541a167c0
commit
b0eb80a2d4
37 files changed
+6279
-4732
No files matched your search
@@ -678,16 +678,6 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序号" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.orderNum"
|
||||
placeholder="排序号"
|
||||
class="inner-cell-input"
|
||||
@change="saveDataCollections"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="saveDataCollections" />
|
||||
@@ -703,6 +693,12 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div class="route-config-right-footer">
|
||||
<el-button class="btn-main" type="primary" :loading="manualSaveLoading" @click="handleManualSave">
|
||||
<el-icon><Check /></el-icon>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="route-config-right-empty">
|
||||
@@ -1021,6 +1017,16 @@
|
||||
reagentMasterById.value = next
|
||||
}
|
||||
|
||||
/** 通过 reagentCode 在已缓存的主表数据中反查 masterId */
|
||||
function resolveReagentMasterId(row: Record<string, any>): string | null {
|
||||
const code = (row.reagentCode || '').trim()
|
||||
if (!code) return null
|
||||
for (const master of Object.values(reagentMasterById.value)) {
|
||||
if ((master.reagentCode || '').trim() === code) return master.id
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function searchReagentOptions(query: string) {
|
||||
reagentOptionsLoading.value = true
|
||||
try {
|
||||
@@ -1054,6 +1060,14 @@
|
||||
}
|
||||
return
|
||||
}
|
||||
// reagentId 为空(后端不存储该字段)— 通过 reagentCode 反查主表真实 ID
|
||||
const masterId = resolveReagentMasterId(row)
|
||||
if (masterId) {
|
||||
row.reagentId = masterId
|
||||
// 主表选项已在 searchReagentOptions 中加载,无需重复添加
|
||||
return
|
||||
}
|
||||
// 实在找不到主表记录才走 legacy 兜底
|
||||
const code = (row.reagentCode || '').trim()
|
||||
const name = (row.reagentName || '').trim()
|
||||
if (!code && !name) return
|
||||
@@ -1080,6 +1094,16 @@
|
||||
saveReagents()
|
||||
return
|
||||
}
|
||||
// 重复校验:同一试剂 ID 不可出现多次
|
||||
const reagents = selectedStep.value?.reagents || []
|
||||
const duplicate = reagents.find(
|
||||
(r: Record<string, any>) => r !== row && r.reagentId === reagentId
|
||||
)
|
||||
if (duplicate) {
|
||||
ElMessage.warning('该试剂已添加,不可重复选择')
|
||||
row.reagentId = ''
|
||||
return
|
||||
}
|
||||
const master = reagentMasterById.value[reagentId]
|
||||
if (!master) {
|
||||
saveReagents()
|
||||
@@ -1111,6 +1135,16 @@
|
||||
consumableMasterById.value = next
|
||||
}
|
||||
|
||||
/** 通过 consumableCode 在已缓存的主表数据中反查 masterId */
|
||||
function resolveConsumableMasterId(row: Record<string, any>): string | null {
|
||||
const code = (row.consumableCode || '').trim()
|
||||
if (!code) return null
|
||||
for (const master of Object.values(consumableMasterById.value)) {
|
||||
if ((master.consumableCode || '').trim() === code) return master.id
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function searchConsumableOptions(query: string) {
|
||||
consumableOptionsLoading.value = true
|
||||
try {
|
||||
@@ -1143,6 +1177,14 @@
|
||||
}
|
||||
return
|
||||
}
|
||||
// consumableId 为空(后端不存储该字段)— 通过 consumableCode 反查主表真实 ID
|
||||
const masterId = resolveConsumableMasterId(row)
|
||||
if (masterId) {
|
||||
row.consumableId = masterId
|
||||
// 主表选项已在 searchConsumableOptions 中加载,无需重复添加
|
||||
return
|
||||
}
|
||||
// 实在找不到主表记录才走 legacy 兜底
|
||||
const code = (row.consumableCode || '').trim()
|
||||
const name = (row.consumableName || '').trim()
|
||||
if (!code && !name) return
|
||||
@@ -1169,6 +1211,16 @@
|
||||
saveConsumables()
|
||||
return
|
||||
}
|
||||
// 重复校验:同一耗材 ID 不可出现多次
|
||||
const consumables = selectedStep.value?.consumables || []
|
||||
const duplicate = consumables.find(
|
||||
(c: Record<string, any>) => c !== row && c.consumableId === consumableId
|
||||
)
|
||||
if (duplicate) {
|
||||
ElMessage.warning('该耗材已添加,不可重复选择')
|
||||
row.consumableId = ''
|
||||
return
|
||||
}
|
||||
const master = consumableMasterById.value[consumableId]
|
||||
if (!master) {
|
||||
saveConsumables()
|
||||
@@ -1449,16 +1501,32 @@
|
||||
|
||||
async function saveReagents() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
const reagents = selectedStep.value.reagents || []
|
||||
// 按 reagentId(如有)或 reagentCode 检查重复
|
||||
const seen = new Map<string, number>()
|
||||
for (let i = 0; i < reagents.length; i++) {
|
||||
const r = reagents[i]
|
||||
const key = r.reagentId
|
||||
? String(r.reagentId)
|
||||
: (r.reagentCode || '').trim()
|
||||
if (!key) continue
|
||||
if (seen.has(key)) {
|
||||
ElMessage.warning(`试剂"${r.reagentName || r.reagentCode}"重复,请移除重复项`)
|
||||
return
|
||||
}
|
||||
seen.set(key, i)
|
||||
}
|
||||
try {
|
||||
await fetchUpdateStepReagents({
|
||||
stepId: selectedStep.value.id,
|
||||
items: (selectedStep.value.reagents || []).map((r: Record<string, any>) => ({
|
||||
items: reagents.map((r: Record<string, any>, idx: number) => ({
|
||||
reagentCode: r.reagentCode,
|
||||
reagentName: r.reagentName,
|
||||
spec: r.spec,
|
||||
standardUsage: r.standardUsage,
|
||||
unit: r.unit,
|
||||
isKeyMaterial: toApiFlag(r.isKeyMaterial),
|
||||
orderNum: idx,
|
||||
remark: r.remark || ''
|
||||
}))
|
||||
})
|
||||
@@ -1491,16 +1559,32 @@
|
||||
|
||||
async function saveConsumables() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
const consumables = selectedStep.value.consumables || []
|
||||
// 按 consumableId(如有)或 consumableCode 检查重复
|
||||
const seen = new Map<string, number>()
|
||||
for (let i = 0; i < consumables.length; i++) {
|
||||
const c = consumables[i]
|
||||
const key = c.consumableId
|
||||
? String(c.consumableId)
|
||||
: (c.consumableCode || '').trim()
|
||||
if (!key) continue
|
||||
if (seen.has(key)) {
|
||||
ElMessage.warning(`耗材"${c.consumableName || c.consumableCode}"重复,请移除重复项`)
|
||||
return
|
||||
}
|
||||
seen.set(key, i)
|
||||
}
|
||||
try {
|
||||
await fetchUpdateStepConsumables({
|
||||
stepId: selectedStep.value.id,
|
||||
items: (selectedStep.value.consumables || []).map((c: Record<string, any>) => ({
|
||||
items: consumables.map((c: Record<string, any>, idx: number) => ({
|
||||
consumableCode: c.consumableCode,
|
||||
consumableName: c.consumableName,
|
||||
specModel: c.specModel,
|
||||
standardUsage: c.standardUsage,
|
||||
unit: c.unit,
|
||||
isRequired: toApiFlag(c.isRequired),
|
||||
orderNum: idx,
|
||||
remark: c.remark || ''
|
||||
}))
|
||||
})
|
||||
@@ -1532,10 +1616,11 @@
|
||||
try {
|
||||
await fetchUpdateStepOutputs({
|
||||
stepId: selectedStep.value.id,
|
||||
items: (selectedStep.value.outputs || []).map((o: Record<string, any>) => ({
|
||||
items: (selectedStep.value.outputs || []).map((o: Record<string, any>, idx: number) => ({
|
||||
outputTypeId: o.outputTypeId,
|
||||
outputName: o.outputName,
|
||||
outputBarcode: o.outputBarcode,
|
||||
orderNum: idx,
|
||||
remark: o.remark || ''
|
||||
}))
|
||||
})
|
||||
@@ -1573,10 +1658,10 @@
|
||||
try {
|
||||
await fetchUpdateStepDataCollections({
|
||||
stepId: selectedStep.value.id,
|
||||
items: (selectedStep.value.dataCollections || []).map((d: Record<string, any>) => ({
|
||||
items: (selectedStep.value.dataCollections || []).map((d: Record<string, any>, idx: number) => ({
|
||||
collectionName: d.collectionName,
|
||||
unit: d.unit,
|
||||
orderNum: parseOrderNum(d.orderNum),
|
||||
orderNum: idx,
|
||||
remark: d.remark || ''
|
||||
}))
|
||||
})
|
||||
@@ -1585,6 +1670,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 右侧区域手动保存
|
||||
// ---------------------------------------------------------------------------
|
||||
const manualSaveLoading = ref(false)
|
||||
|
||||
async function handleManualSave() {
|
||||
if (!selectedStep.value) return
|
||||
manualSaveLoading.value = true
|
||||
try {
|
||||
await Promise.all([
|
||||
saveStepBasic(),
|
||||
saveReagents(),
|
||||
saveConsumables(),
|
||||
saveOutputs(),
|
||||
saveDataCollections()
|
||||
])
|
||||
ElMessage.success('保存成功')
|
||||
} catch {
|
||||
ElMessage.error('保存失败,请重试')
|
||||
} finally {
|
||||
manualSaveLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 初始化
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1874,6 +1983,14 @@
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.route-config-right-footer {
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.route-config-right-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -7,23 +7,23 @@
|
||||
<el-form label-position="top" class="route-config-step-form">
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="工序名称">
|
||||
<el-input v-model="editor.selectedStep.value.processName" @change="editor.saveStepBasic" />
|
||||
<el-input v-model="editor.selectedStep.value.processName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工序分类">
|
||||
<el-select v-model="editor.selectedStep.value.categoryId" placeholder="选择分类" clearable @change="editor.saveStepBasic">
|
||||
<el-select v-model="editor.selectedStep.value.categoryId" placeholder="选择分类" clearable>
|
||||
<el-option v-for="cat in editor.categoryOptions.value" :key="cat.id"
|
||||
:label="cat.categoryName || cat.name" :value="cat.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="form-col-span-2">
|
||||
<el-input v-model="editor.selectedStep.value.description" type="textarea" :rows="2"
|
||||
placeholder="工序描述" @change="editor.saveStepBasic" />
|
||||
placeholder="工序描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称">
|
||||
<el-input v-model="editor.selectedStep.value.equipmentName" @change="editor.saveStepBasic" />
|
||||
<el-input v-model="editor.selectedStep.value.equipmentName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预计时长(min)">
|
||||
<el-input v-model="editor.selectedStep.value.estimatedDuration" placeholder="如 2h" @change="editor.saveStepBasic" />
|
||||
<el-input v-model="editor.selectedStep.value.estimatedDuration" placeholder="如 2h" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联SOP" class="form-col-span-2">
|
||||
<el-select v-model="editor.sopIdsArr.value" multiple placeholder="选择SOP文档" filterable
|
||||
@@ -46,7 +46,7 @@
|
||||
<el-table-column type="index" label="序号" width="56" align="center" :index="(i: number) => i + 1" />
|
||||
<el-table-column label="试剂编码" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.reagentCode" placeholder="编码" class="inner-cell-input" @change="editor.saveReagents" />
|
||||
<el-input v-model="row.reagentCode" placeholder="编码" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="试剂名称" min-width="160" align="center">
|
||||
@@ -62,12 +62,12 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="规格" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.spec" placeholder="规格" class="inner-cell-input" @change="editor.saveReagents" />
|
||||
<el-input v-model="row.spec" placeholder="规格" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标准用量" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" @change="editor.saveReagents" />
|
||||
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center">
|
||||
@@ -75,20 +75,19 @@
|
||||
<el-select v-model="row.unit" placeholder="请选择单位" clearable filterable remote
|
||||
reserve-keyword class="inner-cell-select" :loading="editor.itemUnitOptionsLoading.value"
|
||||
:remote-method="editor.searchItemUnitOptions"
|
||||
@visible-change="(vis: boolean) => vis && editor.searchItemUnitOptions('')"
|
||||
@change="editor.saveReagents">
|
||||
@visible-change="(vis: boolean) => vis && editor.searchItemUnitOptions('')">
|
||||
<el-option v-for="item in editor.itemUnitOptions.value" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="关键物料" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isKeyMaterial" @change="editor.saveReagents" />
|
||||
<el-switch v-model="row.isKeyMaterial" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="editor.saveReagents" />
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
@@ -110,7 +109,7 @@
|
||||
<el-table-column type="index" label="序号" width="56" align="center" :index="(i: number) => i + 1" />
|
||||
<el-table-column label="耗材编码" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.consumableCode" placeholder="编码" class="inner-cell-input" @change="editor.saveConsumables" />
|
||||
<el-input v-model="row.consumableCode" placeholder="编码" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="耗材名称" min-width="160" align="center">
|
||||
@@ -126,12 +125,12 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="规格型号" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.specModel" placeholder="规格型号" class="inner-cell-input" @change="editor.saveConsumables" />
|
||||
<el-input v-model="row.specModel" placeholder="规格型号" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标准用量" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" @change="editor.saveConsumables" />
|
||||
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center">
|
||||
@@ -139,20 +138,19 @@
|
||||
<el-select v-model="row.unit" placeholder="请选择单位" clearable filterable remote
|
||||
reserve-keyword class="inner-cell-select" :loading="editor.itemUnitOptionsLoading.value"
|
||||
:remote-method="editor.searchItemUnitOptions"
|
||||
@visible-change="(vis: boolean) => vis && editor.searchItemUnitOptions('')"
|
||||
@change="editor.saveConsumables">
|
||||
@visible-change="(vis: boolean) => vis && editor.searchItemUnitOptions('')">
|
||||
<el-option v-for="item in editor.itemUnitOptions.value" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="必需" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isRequired" @change="editor.saveConsumables" />
|
||||
<el-switch v-model="row.isRequired" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="editor.saveConsumables" />
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
@@ -174,8 +172,7 @@
|
||||
<el-table-column type="index" label="序号" width="56" align="center" :index="(i: number) => i + 1" />
|
||||
<el-table-column label="产出类型" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.outputTypeId" placeholder="选择类型" filterable class="inner-cell-select"
|
||||
@change="editor.saveOutputs">
|
||||
<el-select v-model="row.outputTypeId" placeholder="选择类型" filterable class="inner-cell-select">
|
||||
<el-option v-for="ot in editor.outputTypeOptions.value" :key="ot.id"
|
||||
:label="ot.typeName || ot.name" :value="ot.id" />
|
||||
</el-select>
|
||||
@@ -183,17 +180,17 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="名称" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.outputName" placeholder="名称" class="inner-cell-input" @change="editor.saveOutputs" />
|
||||
<el-input v-model="row.outputName" placeholder="名称" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="条码" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.outputBarcode" placeholder="条码" class="inner-cell-input" @change="editor.saveOutputs" />
|
||||
<el-input v-model="row.outputBarcode" placeholder="条码" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="editor.saveOutputs" />
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
@@ -215,8 +212,7 @@
|
||||
<el-table-column type="index" label="序号" width="56" align="center" :index="(i: number) => i + 1" />
|
||||
<el-table-column label="采集名称" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.collectionName" placeholder="采集项名称" class="inner-cell-input"
|
||||
@change="editor.saveDataCollections" />
|
||||
<el-input v-model="row.collectionName" placeholder="采集项名称" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center">
|
||||
@@ -224,20 +220,19 @@
|
||||
<el-select v-model="row.unit" placeholder="请选择单位" clearable filterable remote
|
||||
reserve-keyword class="inner-cell-select" :loading="editor.itemUnitOptionsLoading.value"
|
||||
:remote-method="editor.searchItemUnitOptions"
|
||||
@visible-change="(vis: boolean) => vis && editor.searchItemUnitOptions('')"
|
||||
@change="editor.saveDataCollections">
|
||||
@visible-change="(vis: boolean) => vis && editor.searchItemUnitOptions('')">
|
||||
<el-option v-for="item in editor.itemUnitOptions.value" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序号" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.orderNum" placeholder="排序号" class="inner-cell-input" @change="editor.saveDataCollections" />
|
||||
<el-input v-model="row.orderNum" placeholder="排序号" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="editor.saveDataCollections" />
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
@@ -250,6 +245,12 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div class="route-config-right-footer">
|
||||
<el-button class="btn-main" type="primary" :loading="editor.manualSaveLoading.value" @click="editor.handleManualSave">
|
||||
<el-icon><Check /></el-icon>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="route-config-right-empty">
|
||||
@@ -261,7 +262,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Rank } from '@element-plus/icons-vue'
|
||||
import { Check, Plus, Rank } from '@element-plus/icons-vue'
|
||||
import { useTaskStepEditor } from './useTaskStepEditor'
|
||||
|
||||
defineProps<{
|
||||
|
||||
@@ -148,6 +148,7 @@
|
||||
<el-dialog v-model="taskSelectVisible" title="生产任务列表" width="780px" destroy-on-close align-center>
|
||||
<div v-loading="taskSelectLoading" style="max-height:60vh;overflow-y:auto">
|
||||
<el-table :data="taskSelectList" stripe style="width:100%">
|
||||
<el-table-column type="index" label="序号" width="68" align="center" :index="(i) => i + 1" />
|
||||
<el-table-column prop="taskName" label="任务名称" min-width="140" />
|
||||
<el-table-column prop="routeName" label="工艺路线" min-width="140">
|
||||
<template #default="{ row }">{{ row.routeName || '—' }}</template>
|
||||
|
||||
@@ -146,10 +146,19 @@ export function useTaskStepEditor() {
|
||||
}
|
||||
}
|
||||
function onReagentRowSelect(row: Record<string, any>, reagentId: string) {
|
||||
if (!reagentId) { row.reagentId = ''; saveReagents(); return }
|
||||
if (String(reagentId).startsWith('legacy:')) { saveReagents(); return }
|
||||
if (!reagentId) { row.reagentId = ''; return }
|
||||
if (String(reagentId).startsWith('legacy:')) { return }
|
||||
// 去重:同一试剂不允许出现多次(排除当前行和 legacy 占位符)
|
||||
const dup = (selectedStep.value?.reagents || []).some(
|
||||
(r: Record<string, any>) => r !== row && r.reagentId === reagentId && !String(r.reagentId).startsWith('legacy:')
|
||||
)
|
||||
if (dup) {
|
||||
ElMessage.warning('该试剂已添加,请勿重复选择')
|
||||
row.reagentId = ''
|
||||
return
|
||||
}
|
||||
const master = reagentMasterById.value[reagentId]
|
||||
if (!master) { saveReagents(); return }
|
||||
if (!master) { return }
|
||||
Object.assign(row, {
|
||||
reagentId: master.id, reagentCode: master.reagentCode || '',
|
||||
reagentName: master.reagentName || '', spec: master.spec || '',
|
||||
@@ -158,7 +167,6 @@ export function useTaskStepEditor() {
|
||||
})
|
||||
ensureItemUnitOption(row.unit)
|
||||
ensureReagentOption(row)
|
||||
saveReagents()
|
||||
}
|
||||
|
||||
function mapConsumableOptionLabel(c: { consumableName?: string; consumableCode?: string }) {
|
||||
@@ -208,8 +216,28 @@ export function useTaskStepEditor() {
|
||||
}
|
||||
}
|
||||
function onConsumableRowSelect(row: Record<string, any>, consumableId: string) {
|
||||
if (!consumableId) { row.consumableId = ''; saveConsumables(); return }
|
||||
if (String(consumableId).startsWith('legacy:')) { saveConsumables(); return }
|
||||
if (!consumableId) { row.consumableId = ''; return }
|
||||
if (String(consumableId).startsWith('legacy:')) { return }
|
||||
// 去重:同一耗材不允许出现多次(排除当前行和 legacy 占位符)
|
||||
const dup = (selectedStep.value?.consumables || []).some(
|
||||
(c: Record<string, any>) => c !== row && c.consumableId === consumableId && !String(c.consumableId).startsWith('legacy:')
|
||||
)
|
||||
if (dup) {
|
||||
ElMessage.warning('该耗材已添加,请勿重复选择')
|
||||
row.consumableId = ''
|
||||
return
|
||||
}
|
||||
const master = consumableMasterById.value[consumableId]
|
||||
if (!master) { return }
|
||||
Object.assign(row, {
|
||||
consumableId: master.id, consumableCode: master.consumableCode || '',
|
||||
consumableName: master.consumableName || '', specModel: master.specModel || '',
|
||||
standardUsage: master.standardUsage ?? '', unit: master.unit || '',
|
||||
remark: master.remark ?? master.description ?? ''
|
||||
})
|
||||
ensureItemUnitOption(row.unit)
|
||||
ensureConsumableOption(row)
|
||||
}
|
||||
const master = consumableMasterById.value[consumableId]
|
||||
if (!master) { saveConsumables(); return }
|
||||
Object.assign(row, {
|
||||
@@ -359,19 +387,18 @@ export function useTaskStepEditor() {
|
||||
if (!selectedStep.value) return
|
||||
try {
|
||||
await fetchUpdateTaskStep(taskId.value, selectedStep.value.id, {
|
||||
processName: selectedStep.value.processName,
|
||||
categoryId: selectedStep.value.categoryId,
|
||||
process_name: selectedStep.value.processName,
|
||||
category_id: selectedStep.value.categoryId,
|
||||
description: selectedStep.value.description,
|
||||
equipmentName: selectedStep.value.equipmentName,
|
||||
estimatedDuration: selectedStep.value.estimatedDuration,
|
||||
sopIds: selectedStep.value.sopIds
|
||||
equipment_name: selectedStep.value.equipmentName,
|
||||
estimated_duration: selectedStep.value.estimatedDuration,
|
||||
sop_ids: selectedStep.value.sopIds
|
||||
})
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
function onSopIdsChange(val: string[]) {
|
||||
if (!selectedStep.value) return
|
||||
selectedStep.value.sopIds = val.join(',')
|
||||
saveStepBasic()
|
||||
}
|
||||
|
||||
function addReagentRow() {
|
||||
@@ -381,19 +408,17 @@ export function useTaskStepEditor() {
|
||||
reagentId: '', reagentCode: '', reagentName: '', spec: '',
|
||||
standardUsage: '', unit: '', isKeyMaterial: false, remark: ''
|
||||
})
|
||||
saveReagents()
|
||||
}
|
||||
function removeReagentRow(i: number) { selectedStep.value?.reagents?.splice(i, 1); saveReagents() }
|
||||
function removeReagentRow(i: number) { selectedStep.value?.reagents?.splice(i, 1) }
|
||||
async function saveReagents() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
try {
|
||||
await fetchReplaceStepReagents(taskId.value, selectedStep.value.id, {
|
||||
items: (selectedStep.value.reagents || []).map((r: Record<string, any>) => ({
|
||||
reagentCode: r.reagentCode, reagentName: r.reagentName, spec: r.spec,
|
||||
standardUsage: r.standardUsage, unit: r.unit,
|
||||
isKeyMaterial: toApiFlag(r.isKeyMaterial), remark: r.remark || ''
|
||||
}))
|
||||
})
|
||||
const items = (selectedStep.value.reagents || []).map((r: Record<string, any>) => ({
|
||||
material_code: r.reagentCode, material_name: r.reagentName, spec: r.spec,
|
||||
standard_usage: r.standardUsage, unit: r.unit,
|
||||
is_key_material: toApiFlag(r.isKeyMaterial), remark: r.remark || ''
|
||||
}))
|
||||
await fetchReplaceStepReagents(taskId.value, selectedStep.value.id, items)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
@@ -404,19 +429,18 @@ export function useTaskStepEditor() {
|
||||
consumableId: '', consumableCode: '', consumableName: '', specModel: '',
|
||||
standardUsage: '', unit: '', isRequired: false, remark: ''
|
||||
})
|
||||
saveConsumables()
|
||||
}
|
||||
function removeConsumableRow(i: number) { selectedStep.value?.consumables?.splice(i, 1); saveConsumables() }
|
||||
function removeConsumableRow(i: number) { selectedStep.value?.consumables?.splice(i, 1) }
|
||||
async function saveConsumables() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
try {
|
||||
await fetchReplaceStepConsumables(taskId.value, selectedStep.value.id, {
|
||||
items: (selectedStep.value.consumables || []).map((c: Record<string, any>) => ({
|
||||
consumableCode: c.consumableCode, consumableName: c.consumableName, specModel: c.specModel,
|
||||
standardUsage: c.standardUsage, unit: c.unit,
|
||||
isRequired: toApiFlag(c.isRequired), remark: c.remark || ''
|
||||
}))
|
||||
})
|
||||
const items = (selectedStep.value.consumables || []).map((c: Record<string, any>) => ({
|
||||
material_code: c.consumableCode, material_name: c.consumableName,
|
||||
spec_model: c.specModel,
|
||||
standard_usage: c.standardUsage, unit: c.unit,
|
||||
is_required: toApiFlag(c.isRequired), remark: c.remark || ''
|
||||
}))
|
||||
await fetchReplaceStepConsumables(taskId.value, selectedStep.value.id, items)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
@@ -424,18 +448,16 @@ export function useTaskStepEditor() {
|
||||
if (!selectedStep.value) return
|
||||
if (!selectedStep.value.outputs) selectedStep.value.outputs = []
|
||||
selectedStep.value.outputs.push({ outputTypeId: '', outputName: '', outputBarcode: '', remark: '' })
|
||||
saveOutputs()
|
||||
}
|
||||
function removeOutputRow(i: number) { selectedStep.value?.outputs?.splice(i, 1); saveOutputs() }
|
||||
function removeOutputRow(i: number) { selectedStep.value?.outputs?.splice(i, 1) }
|
||||
async function saveOutputs() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
try {
|
||||
await fetchReplaceStepOutputs(taskId.value, selectedStep.value.id, {
|
||||
items: (selectedStep.value.outputs || []).map((o: Record<string, any>) => ({
|
||||
outputTypeId: o.outputTypeId, outputName: o.outputName,
|
||||
outputBarcode: o.outputBarcode, remark: o.remark || ''
|
||||
}))
|
||||
})
|
||||
const items = (selectedStep.value.outputs || []).map((o: Record<string, any>) => ({
|
||||
output_type_id: o.outputTypeId, output_name: o.outputName,
|
||||
output_barcode: o.outputBarcode, remark: o.remark || ''
|
||||
}))
|
||||
await fetchReplaceStepOutputs(taskId.value, selectedStep.value.id, items)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
@@ -443,21 +465,39 @@ export function useTaskStepEditor() {
|
||||
if (!selectedStep.value) return
|
||||
if (!selectedStep.value.dataCollections) selectedStep.value.dataCollections = []
|
||||
selectedStep.value.dataCollections.push({ collectionName: '', unit: '', orderNum: 0, remark: '' })
|
||||
saveDataCollections()
|
||||
}
|
||||
function removeDataCollectionRow(i: number) { selectedStep.value?.dataCollections?.splice(i, 1); saveDataCollections() }
|
||||
function removeDataCollectionRow(i: number) { selectedStep.value?.dataCollections?.splice(i, 1) }
|
||||
async function saveDataCollections() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
try {
|
||||
await fetchReplaceStepDataCollection(taskId.value, selectedStep.value.id, {
|
||||
items: (selectedStep.value.dataCollections || []).map((d: Record<string, any>) => ({
|
||||
collectionName: d.collectionName, unit: d.unit,
|
||||
orderNum: parseOrderNum(d.orderNum), remark: d.remark || ''
|
||||
}))
|
||||
})
|
||||
const items = (selectedStep.value.dataCollections || []).map((d: Record<string, any>) => ({
|
||||
collection_name: d.collectionName, unit: d.unit,
|
||||
collection_value: '', remark: d.remark || ''
|
||||
}))
|
||||
await fetchReplaceStepDataCollection(taskId.value, selectedStep.value.id, items)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
const manualSaveLoading = ref(false)
|
||||
async function handleManualSave() {
|
||||
if (!selectedStep.value) return
|
||||
manualSaveLoading.value = true
|
||||
try {
|
||||
await Promise.all([
|
||||
saveStepBasic(),
|
||||
saveReagents(),
|
||||
saveConsumables(),
|
||||
saveOutputs(),
|
||||
saveDataCollections()
|
||||
])
|
||||
ElMessage.success('保存成功')
|
||||
} catch {
|
||||
ElMessage.error('保存失败,请重试')
|
||||
} finally {
|
||||
manualSaveLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function initEditor(id: string) {
|
||||
taskId.value = id
|
||||
selectedStepId.value = ''
|
||||
@@ -491,6 +531,7 @@ export function useTaskStepEditor() {
|
||||
addConsumableRow, removeConsumableRow, saveConsumables,
|
||||
addOutputRow, removeOutputRow, saveOutputs,
|
||||
addDataCollectionRow, removeDataCollectionRow, saveDataCollections,
|
||||
manualSaveLoading, handleManualSave,
|
||||
initEditor
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user