Sfoglia il codice sorgente

feat(web-admin): enable copy-first and batch slot fill on task output dialog

Speed up multi-line cell product inbound entry in production task execution.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 mesi fa
parent
commit
3079d5da7e

+ 69 - 2
web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue

@@ -179,6 +179,16 @@
           <div class="product-lines-head">
             <span class="product-lines-title">入库明细</span>
             <span class="form-label-tip">{{ productLinesHint }}</span>
+            <div class="product-lines-actions">
+              <el-button class="btn-ghost" @click="handleCopyFirstLineAttrs">
+                <el-icon><CopyDocument /></el-icon>
+                复制首行属性
+              </el-button>
+              <el-button class="btn-ghost" @click="openBatchSlotFill">
+                <el-icon><Grid /></el-icon>
+                批量填储物格
+              </el-button>
+            </div>
           </div>
           <el-table
             :data="productLines"
@@ -286,10 +296,17 @@
       </el-button>
     </template>
   </el-dialog>
+  <ProductLinesBatchSlotFillDialog
+    v-model="batchSlotFillVisible"
+    :empty-line-count="emptyStorageSlotLineCount"
+    :form-used-slot-ids="formUsedStorageSlotIds"
+    :occupied-slot-ids="occupiedSlotIds || []"
+    @filled="onBatchSlotFilled"
+  />
 </template>
 
 <script setup lang="ts">
-  import { Close, Check, MagicStick } from '@element-plus/icons-vue'
+  import { Close, Check, MagicStick, CopyDocument, Grid } from '@element-plus/icons-vue'
   import { ElMessage } from 'element-plus'
   import { fetchProductionProductTypeList } from '@/api/production'
   import { fetchMaxPassageProductSeq, fetchMaxProductSuffixSeq, fetchCheckCellProductCode } from '@/api/cell-product'
@@ -310,6 +327,11 @@
     validateUniqueStorageSlotIds
   } from '@/utils/storage-slot-options'
   import type { StorageSlotSelectOption } from '@/utils/storage-slot-options'
+  import {
+    copyFirstProductLineAttrs,
+    applySlotIdsToEmptyLines
+  } from '@/utils/storage-slot-range-fill'
+  import ProductLinesBatchSlotFillDialog from '@/components/business/product-lines-batch-slot-fill-dialog/index.vue'
 
   defineOptions({ name: 'TaskOutputProductDialog' })
 
@@ -483,9 +505,47 @@
   const submitLoading = ref(false)
   const originalProductCode = ref('')
   const productLines = ref<ProductLine[]>([createEmptyProductLine()])
+  const batchSlotFillVisible = ref(false)
   let lineCodeSyncToken = 0
   let skipLineCodeAutoSync = false
 
+  const emptyStorageSlotLineCount = computed(
+    () => productLines.value.filter((l) => !String(l.storageSlotId || '').trim()).length
+  )
+
+  const formUsedStorageSlotIds = computed(() =>
+    productLines.value.map((l) => l.storageSlotId).filter((id) => String(id || '').trim())
+  )
+
+  function handleCopyFirstLineAttrs() {
+    const r = copyFirstProductLineAttrs(productLines.value)
+    if (!r.ok) {
+      ElMessage.warning(r.message)
+      return
+    }
+    ElMessage.success(`已将首行属性复制到其余 ${r.filledCount} 行`)
+  }
+
+  function openBatchSlotFill() {
+    if (emptyStorageSlotLineCount.value <= 0) {
+      ElMessage.warning('没有可填充的空行')
+      return
+    }
+    batchSlotFillVisible.value = true
+  }
+
+  function onBatchSlotFilled(payload: {
+    slotIds: string[]
+    options: Array<{ id: string; name: string; facilityNodeId?: string }>
+  }) {
+    const n = applySlotIdsToEmptyLines(productLines.value, payload.slotIds)
+    storageSlotOptions.value = ensureStorageSlotsInOptions(
+      storageSlotOptions.value,
+      payload.options
+    )
+    ElMessage.success(`已为 ${n} 行填充储物格`)
+  }
+
   const formData = reactive({
     cellProductId: '',
     productCode: '',
@@ -1012,7 +1072,7 @@
 
   .product-lines-head {
     display: flex;
-    align-items: baseline;
+    align-items: center;
     flex-wrap: wrap;
     gap: 8px;
     margin-bottom: 10px;
@@ -1024,6 +1084,13 @@
     color: var(--mint-text, #334155);
   }
 
+  .product-lines-actions {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    margin-left: auto;
+  }
+
   .product-lines-table {
     width: 100%;
   }