heshaofeng
10 天以前 48710581fbbdd40eb3a743d91fa04e81531ba2ab
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/BatchPickingConfirm.vue
@@ -4,29 +4,8 @@
      <el-page-header @back="goBack">
        <template #content>
          <span class="title">出库拣选确认 - {{ this.$route.query.orderNo }}</span>
          <el-tag v-if="currentBatchNo" type="success" style="margin-left: 10px;">
            å½“前批次: {{ currentBatchNo }}
          </el-tag>
        </template>
      </el-page-header>
    </div>
    <!-- æ‰¹æ¬¡æ“ä½œåŒºåŸŸ -->
    <div class="batch-operations">
      <el-card>
        <div class="batch-actions">
          <el-button type="primary" @click="openBatchAllocateDialog">分批分配</el-button>
          <el-select v-model="selectedBatchNo" placeholder="选择批次" @change="onBatchChange" style="width: 200px; margin-left: 10px;">
            <el-option
              v-for="batch in batchList"
              :key="batch.batchNo"
              :label="`${batch.batchNo} (${batch.batchStatusText})`"
              :value="batch.batchNo">
            </el-option>
          </el-select>
          <el-button type="info" @click="refreshBatchList">刷新批次</el-button>
        </div>
      </el-card>
    </div>
    <!-- æ‰«ç åŒºåŸŸ -->
@@ -52,21 +31,6 @@
          <el-button type="info" @click="openRevertSplitDialog">撤销拆包</el-button>
          <el-button type="info" @click="handleEmptyPallet">取空箱</el-button>
          <el-button type="primary" @click="openBatchReturnDialog">回库</el-button>
        </div>
      </el-card>
    </div>
    <!-- æ‰¹æ¬¡æ±‡æ€»ä¿¡æ¯ -->
    <div class="batch-summary-area" v-if="currentBatchNo">
      <el-card>
        <div class="batch-summary-info">
          <el-tag type="info">批次号: {{ batchSummary.batchNo }}</el-tag>
          <el-tag :type="getBatchStatusType(batchSummary.batchStatus)">
            {{ batchSummary.batchStatusText }}
          </el-tag>
          <el-tag type="warning">分配数量: {{ batchSummary.batchQuantity }}</el-tag>
          <el-tag type="success">完成数量: {{ batchSummary.completedQuantity }}</el-tag>
          <el-tag type="danger">剩余数量: {{ batchSummary.remainingQuantity }}</el-tag>
        </div>
      </el-card>
    </div>
@@ -119,7 +83,7 @@
              <el-table-column type="selection" width="55"></el-table-column>
              <el-table-column prop="materielCode" label="物料编码" width="120"></el-table-column>
              <el-table-column prop="pickedQty" label="已拣数量" width="100"></el-table-column>
              <el-table-column prop="locationCode" label="货位" width="100"></el-table-column>
              <!-- <el-table-column prop="locationCode" label="货位" width="100"></el-table-column> -->
              <el-table-column prop="currentBarcode" label="条码"></el-table-column>
            </el-table>
          </el-card>
@@ -127,54 +91,8 @@
      </el-row>
    </div>
    <!-- åˆ†æ‰¹åˆ†é…å¼¹çª— -->
    <div v-if="showBatchAllocateDialog" class="custom-dialog-overlay">
      <div class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
            <h3>分批分配库存</h3>
            <el-button type="text" @click="closeBatchAllocateDialog" class="close-button">×</el-button>
          </div>
          <div class="custom-dialog-body">
            <el-form :model="batchAllocateForm" :rules="batchAllocateFormRules" ref="batchAllocateFormRef" label-width="100px">
              <el-form-item label="订单编号">
                <el-input v-model="batchAllocateForm.orderNo" disabled></el-input>
              </el-form-item>
              <el-form-item label="物料明细" prop="orderDetailId">
                <el-select v-model="batchAllocateForm.orderDetailId" placeholder="选择物料明细" style="width: 100%">
                  <el-option
                    v-for="detail in allocatableDetails"
                    :key="detail.id"
                    :label="`${detail.materielCode} - éœ€æ±‚:${detail.needOutQuantity} å·²åˆ†é…:${detail.allocatedQuantity} å¯åˆ†é…:${detail.availableQuantity}`"
                    :value="detail.id">
                  </el-option>
                </el-select>
              </el-form-item>
              <el-form-item label="分配数量" prop="batchQuantity">
                <el-input-number
                  v-model="batchAllocateForm.batchQuantity"
                  :min="0.01"
                  :precision="2"
                  :step="1"
                  style="width: 100%"
                  placeholder="输入分配数量">
                </el-input-number>
              </el-form-item>
              <el-form-item label="可分配数量">
                <el-input :value="getAvailableQuantity()" disabled></el-input>
              </el-form-item>
            </el-form>
          </div>
          <div class="custom-dialog-footer">
            <el-button @click="closeBatchAllocateDialog">取消</el-button>
            <el-button type="primary" @click="handleBatchAllocate" :loading="batchAllocateLoading">确认分配</el-button>
          </div>
        </div>
      </div>
    </div>
    <!-- æ‹†åŒ…弹窗 -->
    <div v-if="showCustomSplitDialog" class="custom-dialog-overlay">
    <div v-if="showCustomSplitDialog" class="custom-dialog-overlay" style="z-index: 2000;">
      <div class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
@@ -186,20 +104,28 @@
              <el-form-item label="订单编号">
                <el-input v-model="splitForm.orderNo" disabled></el-input>
              </el-form-item>
              <el-form-item label="批次编号">
                <el-input v-model="splitForm.batchNo" disabled></el-input>
              </el-form-item>
              <el-form-item label="托盘编号">
                <el-input v-model="splitForm.palletCode" disabled></el-input>
              </el-form-item>
              <el-form-item label="原条码" prop="originalBarcode">
                <el-input
                  v-model="splitForm.originalBarcode"
                  placeholder="扫描原条码"
                  @keyup.enter.native="onSplitBarcodeScan"
                  @change="onSplitBarcodeScan"
                  clearable>
                </el-input>
                <div style="display: flex; align-items: center; gap: 10px;">
                  <el-input
                    v-model="splitForm.originalBarcode"
                    placeholder="扫描原条码"
                    @keyup.enter.native="onSplitBarcodeScan"
                    @change="onSplitBarcodeScan"
                    clearable
                    style="flex: 1;">
                  </el-input>
                  <!-- æ–°å¢žï¼šæŸ¥çœ‹æ‹†åŒ…链按钮 -->
                  <el-button
  type="primary"
  @click="viewSplitChainFromSplit(splitForm.originalBarcode)"
  :disabled="!splitForm.originalBarcode"
  :loading="splitChainLoading">
  æŸ¥çœ‹æ‹†åŒ…链
</el-button>
                </div>
              </el-form-item>
              <el-form-item label="物料编码">
                <el-input v-model="splitForm.materielCode" disabled></el-input>
@@ -228,7 +154,7 @@
    </div>
    <!-- æ’¤é”€æ‹†åŒ…弹窗 -->
    <div v-if="showRevertSplitDialog" class="custom-dialog-overlay">
    <div v-if="showRevertSplitDialog" class="custom-dialog-overlay"  style="z-index: 2001;">
      <div class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
@@ -238,15 +164,46 @@
          <div class="custom-dialog-body">
            <el-form :model="revertSplitForm" :rules="revertSplitFormRules" ref="revertSplitFormRef" label-width="100px">
              <el-form-item label="新条码" prop="newBarcode">
                <el-input
                  v-model="revertSplitForm.newBarcode"
                  placeholder="扫描新条码"
                  @keyup.enter.native="onRevertSplitBarcodeScan"
                  @change="onRevertSplitBarcodeScan"
                  clearable>
                </el-input>
                <div style="display: flex; align-items: center; gap: 10px;">
                  <el-input
                    v-model="revertSplitForm.newBarcode"
                    placeholder="扫描新条码"
                    @keyup.enter.native="onRevertSplitBarcodeScan"
                    @change="onRevertSplitBarcodeScan"
                    clearable
                    style="flex: 1;">
                  </el-input>
                  <!-- æ–°å¢žï¼šæŸ¥çœ‹æ‹†åŒ…链按钮 -->
                  <el-button
                    type="primary"
                    @click="viewSplitChain(revertSplitForm.newBarcode)"
                    :disabled="!revertSplitForm.newBarcode">
                    æŸ¥çœ‹æ‹†åŒ…链
                  </el-button>
                </div>
              </el-form-item>
            </el-form>
            <!-- æ–°å¢žï¼šæ‹†åŒ…链简要信息显示 -->
            <div v-if="splitChainInfo.splitChain && splitChainInfo.splitChain.length > 0"
                 style="margin-top: 15px; padding: 10px; background: #f0f9ff; border-radius: 4px;">
              <div style="font-size: 14px; color: #606266;">
                <div>拆包链信息: å…± {{ splitChainInfo.totalSplitTimes }} æ¬¡æ‹†åŒ…</div>
                <div style="margin-top: 5px;">
                  <el-tag
                    v-for="item in splitChainInfo.splitChain.slice(0, 3)"
                    :key="item.newBarcode"
                    :type="item.isReverted ? 'success' : 'primary'"
                    size="small"
                    style="margin-right: 5px;">
                    {{ item.newBarcode }} ({{ item.splitQuantity }})
                  </el-tag>
                  <span v-if="splitChainInfo.splitChain.length > 3" style="color: #909399;">
                    ç­‰ {{ splitChainInfo.splitChain.length }} ä¸ªæ¡ç 
                  </span>
                </div>
              </div>
            </div>
          </div>
          <div class="custom-dialog-footer">
            <el-button @click="closeRevertSplitDialog">取消</el-button>
@@ -256,12 +213,136 @@
      </div>
    </div>
    <!-- æ‹†åŒ…链信息弹窗 -->
<div v-if="showSplitChainDialog" class="custom-dialog-overlay"  style="z-index: 2002;">
  <div class="custom-dialog-wrapper">
    <div class="custom-dialog" style="width: 750px;">
      <div class="custom-dialog-header">
        <h3>拆包链信息</h3>
        <el-button type="text" @click="closeSplitChainDialog" class="close-button">×</el-button>
      </div>
      <div class="custom-dialog-body">
        <!-- æ–°å¢žï¼šæ‹†åŒ…链说明 -->
        <div style="margin-bottom: 15px; padding: 10px; background: #f0f9ff; border-radius: 4px;">
          <div style="display: flex; justify-content: space-between; align-items: center;">
            <div>
              <div style="font-weight: bold; color: #303133;">拆包链说明</div>
              <div style="font-size: 12px; color: #606266; margin-top: 5px;">
                å½“前显示的是从 <el-tag type="primary" size="small">{{ splitChainInfo.originalBarcode }}</el-tag> å¼€å§‹çš„æ‹†åŒ…链
                <br>共 {{ splitChainInfo.totalSplitTimes }} æ¬¡æ‹†åŒ…操作,涉及 {{ splitChainInfo.splitChain.length }} ä¸ªæ¡ç 
              </div>
            </div>
            <el-button
              type="primary"
              size="small"
              @click="findRootChain(splitChainInfo.originalBarcode)"
              v-if="splitChainInfo.chainType !== 'root'">
              æŸ¥æ‰¾å®Œæ•´æ‹†åŒ…链
            </el-button>
          </div>
        </div>
        <div style="margin-bottom: 15px;">
          <el-tag type="info">总拆包次数: {{ splitChainInfo.totalSplitTimes }}</el-tag>
          <el-tag type="warning" style="margin-left: 10px;">
            åŽŸå§‹æ¡ç : {{ splitChainInfo.originalBarcode }}
          </el-tag>
          <el-tag :type="splitChainInfo.chainType === 'root' ? 'success' : 'warning'" style="margin-left: 10px;">
            {{ splitChainInfo.chainType === 'root' ? '完整链' : '分支链' }}
          </el-tag>
        </div>
        <el-table :data="splitChainInfo.splitChain" border height="300">
          <el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
          <el-table-column prop="splitTime" label="拆包时间" width="160">
            <template #default="scope">
              {{ formatDateTime(scope.row.splitTime) }}
            </template>
          </el-table-column>
          <el-table-column prop="originalBarcode" label="原条码" width="140">
            <template #default="scope">
              <el-tag
                :type="scope.row.originalBarcode === splitChainInfo.rootBarcode ? 'success' : 'primary'"
                size="small">
                {{ scope.row.originalBarcode }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="newBarcode" label="新条码" width="140">
            <template #default="scope">
              <el-tag
                :type="scope.row.isReverted ? 'info' : 'warning'"
                size="small">
                {{ scope.row.newBarcode }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="splitQuantity" label="拆包数量" width="100" align="right">
            <template #default="scope">
              {{ scope.row.splitQuantity.toFixed(2) }}
            </template>
          </el-table-column>
          <el-table-column prop="operator" label="操作员" width="100"></el-table-column>
          <el-table-column prop="isReverted" label="状态" width="80" align="center">
            <template #default="scope">
              <el-tag
                :type="scope.row.isReverted ? 'success' : 'danger'"
                size="small">
                {{ scope.row.isReverted ? '已撤销' : '有效' }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="120" align="center">
            <template #default="scope">
              <el-button
                v-if="!scope.row.isReverted"
                type="danger"
                size="mini"
                @click="cancelSingleSplit(scope.row.newBarcode)"
                :disabled="hasPicked(scope.row.newBarcode)">
                å–消
              </el-button>
              <span v-else style="color: #909399;">已撤销</span>
            </template>
          </el-table-column>
        </el-table>
        <!-- æ‰¹é‡æ“ä½œåŒºåŸŸ -->
        <div style="margin-top: 15px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
          <div style="display: flex; justify-content: space-between; align-items: center;">
            <div>
              <span style="font-size: 14px; color: #606266;">
                æ‰¹é‡æ“ä½œ: å¯ä»¥å–消整个拆包链或选择单个拆包记录取消
              </span>
              <div style="font-size: 12px; color: #909399; margin-top: 5px;">
                å®Œæ•´æ‹†åŒ…链包含从最原始条码开始的所有拆包操作
              </div>
            </div>
            <div>
              <el-button
                type="danger"
                @click="cancelWholeSplitChain"
                :disabled="!canCancelWholeChain"
                :loading="revertSplitLoading">
                å–消整个拆包链
              </el-button>
            </div>
          </div>
        </div>
      </div>
      <div class="custom-dialog-footer">
        <el-button @click="closeSplitChainDialog">关闭</el-button>
      </div>
    </div>
  </div>
</div>
    <!-- æ‰¹é‡å›žåº“弹窗 -->
    <div v-if="showBatchReturnDialog" class="custom-dialog-overlay">
    <div v-if="showBatchReturnDialog" class="custom-dialog-overlay"  style="z-index: 2003;">
      <div class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
            <h3>批次回库</h3>
            <h3>托盘回库</h3>
            <el-button type="text" @click="closeBatchReturnDialog" class="close-button">×</el-button>
          </div>
          <div class="custom-dialog-body">
@@ -269,8 +350,8 @@
              <el-form-item label="订单编号">
                <el-input v-model="batchReturnForm.orderNo" disabled></el-input>
              </el-form-item>
              <el-form-item label="批次编号">
                <el-input v-model="batchReturnForm.batchNo" disabled></el-input>
              <el-form-item label="托盘编号">
                <el-input v-model="batchReturnForm.palletCode" disabled></el-input>
              </el-form-item>
              <el-form-item label="未拣选数量">
                <el-input v-model="batchReturnForm.unpickedQuantity" disabled></el-input>
@@ -289,7 +370,7 @@
    </div>
    <!-- å–走空箱弹窗 -->
    <div v-if="showEmptyPalletDialog" class="custom-dialog-overlay">
    <div v-if="showEmptyPalletDialog" class="custom-dialog-overlay"  style="z-index: 2004;">
      <div class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
@@ -324,47 +405,25 @@
  </div>
</template>
<script>
import http from '@/api/http.js'
import { ref, defineComponent } from "vue";
import { defineComponent } from "vue";
import { ElMessage } from 'element-plus' 
import { useRoute } from 'vue-router'
import printView from "@/extension/outbound/extend/printView.vue"
export default defineComponent({
  name: 'BatchOutboundPicking',
  components: {printView},
  data() {
    // éªŒè¯è§„则定义...
    const validateBatchQuantity = (rule, value, callback) => {
      if (value === null || value === undefined || value === '') {
        callback(new Error('请输入分配数量'));
      } else if (value <= 0) {
        callback(new Error('分配数量必须大于0'));
      } else {
        callback();
      }
    };
    const validateOrderDetailId = (rule, value, callback) => {
      if (!value) {
        callback(new Error('请选择物料明细'));
      } else {
        callback();
      }
    };
    return {
      // ä¿æŒæ‰€æœ‰åŽŸå§‹æ•°æ®ç»“æž„ä¸å˜...
      scanData: {
        orderNo: '',
        palletCode: '',
        barcode: '',
        batchNo: ''
        barcode: ''
      },
      currentBatchNo: '', // å½“前批次号
      batchList: [], // æ‰¹æ¬¡åˆ—表
      selectedBatchNo: '', // é€‰ä¸­çš„æ‰¹æ¬¡å·
      batchSummary: {}, // æ‰¹æ¬¡æ±‡æ€»ä¿¡æ¯
      unpickedList: [],
      pickedList: [],
      selectedPickedRows: [],
@@ -375,31 +434,26 @@
      },
      palletStatus: '未知',
      
      // å¼¹çª—状态
      showBatchAllocateDialog: false,
      // å¼¹çª—状态 - å…³é”®ä¿®å¤ï¼šåªå…è®¸ä¸€ä¸ªå¼¹çª—打开
      activeDialog: null, // 'split', 'revert', 'batchReturn', 'emptyPallet', 'splitChain'
      showCustomSplitDialog: false,
      showRevertSplitDialog: false,
      showBatchReturnDialog: false,
      showEmptyPalletDialog: false,
      showSplitChainDialog: false,
      
        // æ·»åŠ é˜²é‡å¤ç‚¹å‡»æ ‡å¿—
      isOpeningDialog: false,
      // åŠ è½½çŠ¶æ€
      batchAllocateLoading: false,
      splitLoading: false,
      revertSplitLoading: false,
      batchReturnLoading: false,
      emptypalletOutLoading: false,
      splitChainLoading: false,
      
      // è¡¨å•数据
      batchAllocateForm: {
        orderNo: '',
        orderDetailId: '',
        batchQuantity: 0
      },
      allocatableDetails: [], // å¯åˆ†é…çš„订单明细
      // è¡¨å•数据...
      splitForm: {
        orderNo: '',
        batchNo: '',
        palletCode: '',
        originalBarcode: '',
        materielCode: '',
@@ -413,7 +467,7 @@
      
      batchReturnForm: {
        orderNo: '',
        batchNo: '',
        palletCode: '',
        unpickedCount: 0,
        unpickedQuantity: 0
      },
@@ -423,177 +477,72 @@
        palletCode: ''
      },
      
      // éªŒè¯è§„则
      batchAllocateFormRules: {
        orderDetailId: [
          { required: true, validator: validateOrderDetailId, trigger: 'change' }
      splitChainInfo: {
        originalBarcode: '',
        totalSplitTimes: 0,
        splitChain: []
      },
      // éªŒè¯è§„则...
      splitFormRules: {
        originalBarcode: [
          { required: true, message: '请输入原条码', trigger: 'blur' }
        ],
        batchQuantity: [
          { required: true, validator: validateBatchQuantity, trigger: 'blur' }
        splitQuantity: [
          { required: true, message: '请输入拆包数量', trigger: 'blur' },
          { type: 'number', min: 0.01, message: '拆包数量必须大于0', trigger: 'blur' }
        ]
      },
      
      // å…¶ä»–验证规则...
      revertSplitFormRules: {
        newBarcode: [
          { required: true, message: '请输入新条码', trigger: 'blur' }
        ]
      },
      emptypalletOutFormRules: {
        palletCode: [
          { required: true, message: '请输入托盘码', trigger: 'blur' }
        ]
      },
      isProcessing: false
    }
  },
  watch: {
    // å…³é”®ä¿®å¤ï¼šç¡®ä¿åŒä¸€æ—¶é—´åªæœ‰ä¸€ä¸ªå¼¹çª—打开
    activeDialog(newVal, oldVal) {
      this.showCustomSplitDialog = newVal === 'split'
      this.showRevertSplitDialog = newVal === 'revert'
      this.showBatchReturnDialog = newVal === 'batchReturn'
      this.showEmptyPalletDialog = newVal === 'emptyPallet'
      this.showSplitChainDialog = newVal === 'splitChain'
    }
  },
  computed: {
    canCancelWholeChain() {
      return this.splitChainInfo.splitChain &&
             this.splitChainInfo.splitChain.some(item => !item.isReverted);
    }
  },
  mounted() {
    if (this.$route.query.orderNo) {
      this.scanData.orderNo = this.$route.query.orderNo;
      this.batchAllocateForm.orderNo = this.$route.query.orderNo;
      this.loadBatchList();
      this.splitForm.orderNo = this.$route.query.orderNo;
      this.batchReturnForm.orderNo = this.$route.query.orderNo;
      this.emptypalletOutForm.orderNo = this.$route.query.orderNo;
    }
    this.$nextTick(() => {
      this.$refs.palletInput.focus();
    // ä½¿ç”¨ requestAnimationFrame ç¡®ä¿é¡µé¢å®Œå…¨åŠ è½½
    requestAnimationFrame(() => {
      if (this.$refs.palletInput) {
        this.$refs.palletInput.focus();
      }
    });
  },
  methods: {
    goBack(){
      this.$router.back()
    },
    // æ‰¹æ¬¡ç›¸å…³æ–¹æ³•
    async loadBatchList() {
      try {
        const res = await http.post('/api/BatchOutbound/order-batch-list', {
          orderNo: this.scanData.orderNo
        });
        if (res.status) {
          this.batchList = res.data || [];
          if (this.batchList.length > 0) {
            this.selectedBatchNo = this.batchList[0].batchNo;
            this.currentBatchNo = this.selectedBatchNo;
            this.scanData.batchNo = this.selectedBatchNo;
            this.loadBatchData();
          }
        }
      } catch (error) {
        this.$message.error('加载批次列表失败');
      }
    },
    async refreshBatchList() {
      await this.loadBatchList();
      this.$message.success('批次列表已刷新');
    },
    onBatchChange(batchNo) {
      this.currentBatchNo = batchNo;
      this.scanData.batchNo = batchNo;
      this.loadBatchData();
    },
    async loadBatchData() {
      if (!this.currentBatchNo) return;
      await this.loadBatchSummary();
      await this.loadUnpickedList();
      await this.loadPickedList();
    },
    async loadBatchSummary() {
      try {
        const res = await http.post('/api/BatchOutbound/batch-summary', {
          orderNo: this.scanData.orderNo,
          batchNo: this.currentBatchNo
        });
        if (res.status) {
          this.batchSummary = res.data || {};
        }
      } catch (error) {
        this.$message.error('加载批次汇总失败');
      }
    },
    async loadUnpickedList() {
      try {
        const res = await http.post('/api/BatchOutbound/batch-unpicked-list', {
          orderNo: this.scanData.orderNo,
          batchNo: this.currentBatchNo
        });
        this.unpickedList = res.data || [];
        this.summary.unpickedCount = this.unpickedList.length;
        this.summary.unpickedQuantity = this.unpickedList.reduce((sum, item) => sum + (item.remainQuantity || 0), 0);
      } catch (error) {
        this.$message.error('加载未拣选列表失败');
      }
    },
    async loadPickedList() {
      try {
        const res = await http.post('/api/BatchOutbound/batch-picked-list', {
          orderNo: this.scanData.orderNo,
          batchNo: this.currentBatchNo
        });
        this.pickedList = res.data || [];
        this.summary.pickedCount = this.pickedList.length;
      } catch (error) {
        this.$message.error('加载已拣选列表失败');
      }
    },
    getBatchStatusType(status) {
      const statusMap = {
        0: 'info', // åˆ†é…ä¸­
        1: 'warning', // æ‰§è¡Œä¸­
        2: 'success', // å·²å®Œæˆ
        3: 'danger' // å·²å›žåº“
      };
      return statusMap[status] || 'info';
    },
    // åˆ†æ‰¹åˆ†é…ç›¸å…³æ–¹æ³•
    async openBatchAllocateDialog() {
      this.showBatchAllocateDialog = true;
      await this.loadAllocatableDetails();
      this.batchAllocateForm.orderDetailId = '';
      this.batchAllocateForm.batchQuantity = 0;
    },
    async loadAllocatableDetails() {
      try {
        const res = await http.post('/api/BatchOutbound/allocatable-order-details', {
          orderNo: this.scanData.orderNo
        });
        if (res.status) {
          this.allocatableDetails = res.data || [];
        }
      } catch (error) {
        this.$message.error('加载可分配明细失败');
      }
    },
    getAvailableQuantity() {
      const detail = this.allocatableDetails.find(d => d.id === this.batchAllocateForm.orderDetailId);
      return detail ? detail.availableQuantity : 0;
    },
    async handleBatchAllocate() {
      if (this.$refs.batchAllocateFormRef) {
        this.$refs.batchAllocateFormRef.validate(async (valid) => {
          if (valid) {
            this.batchAllocateLoading = true;
            try {
              const res = await http.post('/api/BatchOutbound/batch-allocate-stock', this.batchAllocateForm);
              if (res.status) {
                this.$message.success('分批分配成功');
                this.showBatchAllocateDialog = false;
                await this.loadBatchList(); // åˆ·æ–°æ‰¹æ¬¡åˆ—表
              } else {
                this.$message.error(res.message || '分批分配失败');
              }
            } catch (error) {
              this.$message.error('分批分配失败');
            } finally {
              this.batchAllocateLoading = false;
            }
          }
        });
      }
    },
    closeBatchAllocateDialog() {
      this.showBatchAllocateDialog = false;
    },
    // åˆ†æ‹£ç›¸å…³æ–¹æ³•
@@ -606,21 +555,20 @@
        return;
      }
      if (!this.currentBatchNo) {
        this.$message.warning('请先选择批次');
        return;
      }
      this.isProcessing = true;
      
      try {
        const res = await http.post('/api/BatchOutbound/confirm-picking', this.scanData);
        const res = await http.post('/api/OutboundBatchPicking/confirm-picking', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode,
          barcode: this.scanData.barcode
        });
        if (res.status) {
          this.$message.success('拣选确认成功');
          this.scanData.barcode = '';
          await this.loadBatchData();
          if(res.data && res.data.splitResults && res.data.splitResults.length>0){
            this.$refs.childs.open(res.data.splitResults);
          await this.loadPalletData();
          if(res.data && res.data && res.data.length>0){
            this.$refs.childs.open(res.data);
          }
          this.$nextTick(() => {
            this.$refs.barcodeInput.focus();
@@ -637,21 +585,445 @@
      }
    },
    // æ‹†åŒ…相关方法
    openSplitDialog() {
   openSplitDialog() {
      console.log('紧急修复版:打开拆包弹窗');
      if (!this.scanData.palletCode) {
        this.$message.warning('请先扫描托盘码');
        return;
      }
      if (!this.currentBatchNo) {
        this.$message.warning('请先选择批次');
      // 1. å…³é—­æ‰€æœ‰Vue弹窗
      this.closeAllDialogs();
      // 2. å¼ºåˆ¶ä»ŽDOM中移除所有弹窗
      setTimeout(() => {
        const dialogs = document.querySelectorAll('.custom-dialog-overlay');
        dialogs.forEach(dialog => {
          if (dialog.parentNode) {
            dialog.parentNode.removeChild(dialog);
          }
        });
        // å¦‚果已经存在手动弹窗,先移除
        if (this.manualDialog && this.manualDialog.parentNode) {
          this.manualDialog.parentNode.removeChild(this.manualDialog);
        }
        // 3. ç­‰å¾…一帧
        requestAnimationFrame(() => {
          // 4. ç›´æŽ¥åˆ›å»ºæ–°å¼¹çª—,不依赖Vue的响应式系统
          this.createManualSplitDialog();
        });
      }, 10);
    },
    // åˆ›å»ºæ‰‹åŠ¨æ‹†åŒ…å¼¹çª—
    createManualSplitDialog() {
      const newDialog = document.createElement('div');
      newDialog.className = 'custom-dialog-overlay emergency-fix';
      // ç”ŸæˆéšæœºID用于事件绑定
      const dialogId = 'manual-dialog-' + Date.now();
      newDialog.id = dialogId;
      // å­˜å‚¨å¼•用
      this.manualDialog = newDialog;
      // å¼¹çª—内容
      newDialog.innerHTML = `
        <div class="custom-dialog-wrapper">
          <div class="custom-dialog" style="width: 500px;">
            <div class="custom-dialog-header">
              <h3 style="margin: 0; color: #303133;">拆包操作</h3>
              <button class="close-button" onclick="document.getElementById('${dialogId}').remove()" style="
                font-size: 18px;
                color: #909399;
                padding: 0;
                width: 24px;
                height: 24px;
                display: flex;
                align-items: center;
                justify-content: center;
                background: none;
                border: none;
                cursor: pointer;
              ">×</button>
            </div>
            <div class="custom-dialog-body" style="padding: 20px;">
              <div style="margin-bottom: 15px;">
                <div style="display: flex; align-items: center; margin-bottom: 5px;">
                  <span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">订单编号:</span>
                  <input type="text" value="${this.scanData.orderNo}" disabled style="
                    flex: 1;
                    padding: 8px 12px;
                    border: 1px solid #dcdfe6;
                    border-radius: 4px;
                    background-color: #f5f7fa;
                    color: #909399;
                  ">
                </div>
              </div>
              <div style="margin-bottom: 15px;">
                <div style="display: flex; align-items: center; margin-bottom: 5px;">
                  <span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">托盘编号:</span>
                  <input type="text" value="${this.scanData.palletCode}" disabled style="
                    flex: 1;
                    padding: 8px 12px;
                    border: 1px solid #dcdfe6;
                    border-radius: 4px;
                    background-color: #f5f7fa;
                    color: #909399;
                  ">
                </div>
              </div>
              <div style="margin-bottom: 15px;">
                <div style="display: flex; align-items: center; margin-bottom: 5px;">
                  <span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">原条码:</span>
                  <div style="flex: 1; display: flex; align-items: center; gap: 10px;">
                    <input type="text" id="${dialogId}-barcode" placeholder="扫描原条码" style="
                      flex: 1;
                      padding: 8px 12px;
                      border: 1px solid #dcdfe6;
                      border-radius: 4px;
                    ">
                    <button id="${dialogId}-viewChain" style="
                      padding: 8px 16px;
                      background: #409eff;
                      color: white;
                      border: none;
                      border-radius: 4px;
                      cursor: pointer;
                      white-space: nowrap;
                    ">查看拆包链</button>
                  </div>
                </div>
              </div>
              <div style="margin-bottom: 15px;">
                <div style="display: flex; align-items: center; margin-bottom: 5px;">
                  <span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">物料编码:</span>
                  <input type="text" id="${dialogId}-materiel" disabled style="
                    flex: 1;
                    padding: 8px 12px;
                    border: 1px solid #dcdfe6;
                    border-radius: 4px;
                    background-color: #f5f7fa;
                    color: #909399;
                  ">
                </div>
              </div>
              <div style="margin-bottom: 15px;">
                <div style="display: flex; align-items: center; margin-bottom: 5px;">
                  <span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">剩余数量:</span>
                  <input type="text" id="${dialogId}-remain" disabled style="
                    flex: 1;
                    padding: 8px 12px;
                    border: 1px solid #dcdfe6;
                    border-radius: 4px;
                    background-color: #f5f7fa;
                    color: #909399;
                  ">
                </div>
              </div>
              <div style="margin-bottom: 15px;">
                <div style="display: flex; align-items: center; margin-bottom: 5px;">
                  <span style="width: 100px; text-align: right; padding-right: 12px; color: #606266;">拆包数量:</span>
                  <div style="flex: 1;">
                    <input type="number" id="${dialogId}-splitQty" value="1" min="0.01" step="0.01" style="
                      width: 100%;
                      padding: 8px 12px;
                      border: 1px solid #dcdfe6;
                      border-radius: 4px;
                    ">
                  </div>
                </div>
              </div>
            </div>
            <div class="custom-dialog-footer" style="
              padding: 10px 20px 20px;
              text-align: right;
              border-top: 1px solid #ebeef5;
            ">
              <button id="${dialogId}-cancel" style="
                padding: 9px 15px;
                background: white;
                color: #606266;
                border: 1px solid #dcdfe6;
                border-radius: 4px;
                cursor: pointer;
                margin-right: 10px;
              ">取消</button>
              <button id="${dialogId}-confirm" style="
                padding: 9px 15px;
                background: #409eff;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
              ">确认拆包</button>
            </div>
          </div>
        </div>
      `;
      // æ·»åŠ æ ·å¼
      newDialog.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0,0,0,0.5);
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 999999;
      `;
      // å¼¹çª—容器样式
      const wrapper = newDialog.querySelector('.custom-dialog-wrapper');
      if (wrapper) {
        wrapper.style.position = 'relative';
        wrapper.style.zIndex = '1000000';
      }
      // å¼¹çª—内容样式
      const dialog = newDialog.querySelector('.custom-dialog');
      if (dialog) {
        dialog.style.background = 'white';
        dialog.style.borderRadius = '4px';
        dialog.style.maxWidth = '90vw';
        dialog.style.maxHeight = '90vh';
        dialog.style.boxShadow = '0 2px 12px 0 rgba(0, 0, 0, 0.1)';
        dialog.style.overflow = 'auto';
      }
      // å¼¹çª—头部样式
      const header = newDialog.querySelector('.custom-dialog-header');
      if (header) {
        header.style.display = 'flex';
        header.style.justifyContent = 'space-between';
        header.style.alignItems = 'center';
        header.style.padding = '20px 20px 10px';
        header.style.borderBottom = '1px solid #ebeef5';
      }
      document.body.appendChild(newDialog);
      console.log('紧急弹窗已创建');
      // ç»‘定事件
      this.bindManualDialogEvents(dialogId);
      // è‡ªåŠ¨èšç„¦åˆ°æ¡ç è¾“å…¥æ¡†
      setTimeout(() => {
        const barcodeInput = document.getElementById(`${dialogId}-barcode`);
        if (barcodeInput) {
          barcodeInput.focus();
          // æ·»åŠ å›žè½¦é”®ç›‘å¬
          barcodeInput.addEventListener('keyup', (event) => {
            if (event.key === 'Enter') {
              this.onManualSplitBarcodeScan(dialogId);
            }
          });
        }
      }, 100);
    },
    // ç»‘定手动弹窗事件
    bindManualDialogEvents(dialogId) {
      const vm = this; // ä¿å­˜Vue实例引用
      // æŸ¥çœ‹æ‹†åŒ…链按钮
      const viewChainBtn = document.getElementById(`${dialogId}-viewChain`);
      if (viewChainBtn) {
        viewChainBtn.onclick = () => {
          const barcodeInput = document.getElementById(`${dialogId}-barcode`);
          if (barcodeInput && barcodeInput.value.trim()) {
            vm.viewSplitChainFromManualDialog(barcodeInput.value.trim(), dialogId);
          } else {
            ElMessage.warning('请先输入条码');
          }
        };
      }
      // å–消按钮
      const cancelBtn = document.getElementById(`${dialogId}-cancel`);
      if (cancelBtn) {
        cancelBtn.onclick = () => {
          const dialog = document.getElementById(dialogId);
          if (dialog && dialog.parentNode) {
            dialog.parentNode.removeChild(dialog);
          }
        };
      }
      // ç¡®è®¤æ‹†åŒ…按钮
      const confirmBtn = document.getElementById(`${dialogId}-confirm`);
      if (confirmBtn) {
        confirmBtn.onclick = () => {
          vm.handleManualSplitPackage(dialogId);
        };
      }
      // æ¡ç è¾“入框变化事件
      const barcodeInput = document.getElementById(`${dialogId}-barcode`);
      if (barcodeInput) {
        // é˜²æŠ–处理
        let timeout;
        barcodeInput.addEventListener('input', () => {
          clearTimeout(timeout);
          timeout = setTimeout(() => {
            if (barcodeInput.value.trim()) {
              vm.onManualSplitBarcodeScan(dialogId);
            }
          }, 500);
        });
      }
    },
    // æ‰‹åŠ¨å¼¹çª—çš„æ¡ç æ‰«æå¤„ç†
    async onManualSplitBarcodeScan(dialogId) {
      const barcodeInput = document.getElementById(`${dialogId}-barcode`);
      if (!barcodeInput || !barcodeInput.value.trim()) return;
      const barcode = barcodeInput.value.trim();
      try {
        const res = await http.post('/api/OutboundBatchPicking/split-package-info', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode,
          barcode: barcode
        });
        if (res.status) {
          // æ›´æ–°ç‰©æ–™ç¼–码
          const materielInput = document.getElementById(`${dialogId}-materiel`);
          if (materielInput) {
            materielInput.value = res.data.materielCode || '';
          }
          // æ›´æ–°å‰©ä½™æ•°é‡
          const remainInput = document.getElementById(`${dialogId}-remain`);
          if (remainInput) {
            remainInput.value = res.data.remainQuantity || 0;
          }
          // æ›´æ–°æ‹†åŒ…数量(默认为1,不超过剩余数量)
          const splitQtyInput = document.getElementById(`${dialogId}-splitQty`);
          if (splitQtyInput) {
            const maxQty = res.data.remainQuantity || 0;
            splitQtyInput.max = maxQty;
            const currentVal = parseFloat(splitQtyInput.value) || 1;
            if (currentVal > maxQty) {
              splitQtyInput.value = Math.min(1, maxQty);
            }
          }
        } else {
          ElMessage.error(res.message || '获取拆包信息失败');
        }
      } catch (error) {
        console.error('获取拆包信息失败:', error);
        ElMessage.error('获取拆包信息失败');
      }
    },
    // ä»Žæ‰‹åŠ¨å¼¹çª—æŸ¥çœ‹æ‹†åŒ…é“¾
    viewSplitChainFromManualDialog(barcode, dialogId) {
      // å…ˆå…³é—­æ‰‹åŠ¨å¼¹çª—
      const dialog = document.getElementById(dialogId);
      if (dialog && dialog.parentNode) {
        dialog.parentNode.removeChild(dialog);
      }
      // å»¶è¿Ÿä¸€ä¸‹ï¼Œç„¶åŽæ‰“å¼€Vue的拆包链弹窗
      setTimeout(() => {
        this.viewSplitChain(barcode);
      }, 50);
    },
    // å¤„理手动弹窗的拆包操作
    async handleManualSplitPackage(dialogId) {
      const barcodeInput = document.getElementById(`${dialogId}-barcode`);
      const splitQtyInput = document.getElementById(`${dialogId}-splitQty`);
      if (!barcodeInput || !barcodeInput.value.trim()) {
        ElMessage.warning('请输入原条码');
        return;
      }
      this.showCustomSplitDialog = true;
      this.resetSplitForm();
      this.splitForm.orderNo = this.scanData.orderNo;
      this.splitForm.batchNo = this.currentBatchNo;
      this.splitForm.palletCode = this.scanData.palletCode;
      if (!splitQtyInput || !splitQtyInput.value || parseFloat(splitQtyInput.value) <= 0) {
        ElMessage.warning('请输入有效的拆包数量');
        return;
      }
      const originalBarcode = barcodeInput.value.trim();
      const splitQuantity = parseFloat(splitQtyInput.value);
      try {
        // æ˜¾ç¤ºåŠ è½½çŠ¶æ€
        const confirmBtn = document.getElementById(`${dialogId}-confirm`);
        if (confirmBtn) {
          confirmBtn.disabled = true;
          confirmBtn.textContent = '处理中...';
        }
        const res = await http.post('/api/OutboundBatchPicking/split-package', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode,
          originalBarcode: originalBarcode,
          splitQuantity: splitQuantity
        });
        if (res.status) {
          ElMessage.success('拆包成功');
          // å…³é—­æ‰‹åŠ¨å¼¹çª—
          const dialog = document.getElementById(dialogId);
          if (dialog && dialog.parentNode) {
            dialog.parentNode.removeChild(dialog);
          }
          // é‡æ–°åŠ è½½æ•°æ®
          await this.loadPalletData();
        } else {
          ElMessage.error(res.message || '拆包失败');
          // æ¢å¤æŒ‰é’®çŠ¶æ€
          if (confirmBtn) {
            confirmBtn.disabled = false;
            confirmBtn.textContent = '确认拆包';
          }
        }
      } catch (error) {
        console.error('拆包失败:', error);
        ElMessage.error('拆包失败');
        // æ¢å¤æŒ‰é’®çŠ¶æ€
        const confirmBtn = document.getElementById(`${dialogId}-confirm`);
        if (confirmBtn) {
          confirmBtn.disabled = false;
          confirmBtn.textContent = '确认拆包';
        }
      }
    },
      closeAllDialogsImmediately() {
      console.log('立即关闭所有弹窗');
      // ç›´æŽ¥è®¾ç½®ä¸º false,不等待任何异步操作
      this.showCustomSplitDialog = false;
      this.showRevertSplitDialog = false;
      this.showBatchReturnDialog = false;
      this.showEmptyPalletDialog = false;
      this.showSplitChainDialog = false;
      // å¼ºåˆ¶DOM更新
      this.$forceUpdate();
    },
    async onSplitBarcodeScan() {
@@ -659,13 +1031,16 @@
      this.splitForm.originalBarcode = this.splitForm.originalBarcode.replace(/\n/g, '').trim();
      try {
        const res = await http.post('/api/BatchOutbound/split-package-info', {
        const res = await http.post('/api/OutboundBatchPicking/split-package-info', {
          orderNo: this.splitForm.orderNo,
          batchNo: this.splitForm.batchNo,
          palletCode: this.splitForm.palletCode,
          barcode: this.splitForm.originalBarcode
        });
        if (res.status) {
            if(res.data && res.data.length>0){
            this.$refs.childs.open(res.data);
          }
          this.splitForm.materielCode = res.data.materielCode;
          this.splitForm.maxQuantity = res.data.remainQuantity;
          this.splitForm.splitQuantity = Math.min(1, this.splitForm.maxQuantity);
@@ -683,11 +1058,16 @@
          if (valid) {
            this.splitLoading = true;
            try {
              const res = await http.post('/api/BatchOutbound/manual-split-package', this.splitForm);
              const res = await http.post('/api/OutboundBatchPicking/split-package', {
                orderNo: this.splitForm.orderNo,
                palletCode: this.splitForm.palletCode,
                originalBarcode: this.splitForm.originalBarcode,
                splitQuantity: this.splitForm.splitQuantity
              });
              if (res.status) {
                this.$message.success('拆包成功');
                this.showCustomSplitDialog = false;
                await this.loadBatchData();
                this.closeAllDialogs();
                await this.loadPalletData();
              } else {
                this.$message.error(res.message || '拆包失败');
              }
@@ -699,6 +1079,19 @@
          }
        });
      }
    },
    async viewSplitChainFromSplit(barcode) {
      if (!barcode) {
        this.$message.warning('请先输入条码');
        return;
      }
      this.closeAllDialogs();
      setTimeout(() => {
        this.viewSplitChain(barcode);
      }, 50);
    },
    // æ’¤é”€æ‹†åŒ…
@@ -713,15 +1106,15 @@
          if (valid) {
            this.revertSplitLoading = true;
            try {
              const res = await http.post('/api/BatchOutbound/cancel-split-package', {
              const res = await http.post('/api/OutboundBatchPicking/cancel-split', {
                orderNo: this.scanData.orderNo,
                batchNo: this.currentBatchNo,
                palletCode: this.scanData.palletCode,
                newBarcode: this.revertSplitForm.newBarcode
              });
              if (res.status) {
                this.$message.success('撤销拆包成功');
                this.showRevertSplitDialog = false;
                await this.loadBatchData();
                this.closeAllDialogs();
                await this.loadPalletData();
              } else {
                this.$message.error(res.message || '撤销拆包失败');
              }
@@ -735,49 +1128,259 @@
      }
    },
    // å›žåº“相关方法
    openBatchReturnDialog() {
      if (!this.currentBatchNo) {
        this.$message.warning('请先选择批次');
    async findRootChain(currentBarcode) {
      this.splitChainLoading = true;
      try {
        const res = await http.post('/api/OutboundBatchPicking/find-root-split-chain', {
          orderNo: this.scanData.orderNo,
          barcode: currentBarcode
        });
        if (res.status) {
          this.splitChainInfo = res.data;
          this.$message.success('已加载完整拆包链');
        } else {
          this.$message.error(res.message || '查找完整拆包链失败');
        }
      } catch (error) {
        this.$message.error('查找完整拆包链失败');
      } finally {
        this.splitChainLoading = false;
      }
    },
    // æŸ¥çœ‹æ‹†åŒ…链信息
    async viewSplitChain(barcode) {
      if (!barcode) {
        this.$message.warning('请先输入条码');
        return;
      }
      this.showBatchReturnDialog = true;
      this.batchReturnForm.orderNo = this.scanData.orderNo;
      this.batchReturnForm.batchNo = this.currentBatchNo;
      this.batchReturnForm.unpickedCount = this.summary.unpickedCount;
      this.batchReturnForm.unpickedQuantity = this.summary.unpickedQuantity;
      this.splitChainLoading = true;
      try {
        const res = await http.post('/api/OutboundBatchPicking/split-package-chain-info', {
          orderNo: this.scanData.orderNo,
          barcode: barcode
        });
        if (res.status) {
          this.splitChainInfo = res.data;
          this.activeDialog = 'splitChain';
        } else {
          this.$message.error(res.message || '获取拆包链信息失败');
        }
      } catch (error) {
        this.$message.error('获取拆包链信息失败');
      } finally {
        this.splitChainLoading = false;
      }
    },
    // å…³é—­æ‹†åŒ…链信息弹窗
     closeSplitChainDialog() {
      this.showSplitChainDialog = false;
    },
    // å–消单个拆包记录
    async cancelSingleSplit(newBarcode) {
      const originalBarcode = this.splitChainInfo.originalBarcode;
      try {
        await this.$confirm(
          `确定要取消条码 ${newBarcode} çš„æ‹†åŒ…操作吗?`,
          '取消单个拆包',
          {
            confirmButtonText: '确定取消',
            cancelButtonText: '再想想',
            type: 'warning'
          }
        );
        this.revertSplitLoading = true;
        const res = await http.post('/api/OutboundBatchPicking/cancel-split', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode,
          newBarcode: newBarcode
        });
        if (res.status) {
          this.$message.success('取消拆包成功');
          await this.loadPalletData();
          this.closeAllDialogs();
          setTimeout(() => {
            this.viewSplitChain(originalBarcode);
          }, 50);
        } else {
          this.$message.error(res.message || '取消拆包失败');
        }
      } catch (error) {
        if (error !== 'cancel') {
          this.$message.error('取消拆包失败');
        }
      } finally {
        this.revertSplitLoading = false;
      }
    },
    // å–消整个拆包链
    async cancelWholeSplitChain() {
      try {
        await this.$confirm(
          `确定要取消整个拆包链吗?\n这将取消从条码 ${this.splitChainInfo.originalBarcode} å¼€å§‹çš„æ‰€æœ‰æ‹†åŒ…操作。`,
          '取消拆包链确认',
          {
            confirmButtonText: '确定取消',
            cancelButtonText: '再想想',
            type: 'warning',
            center: true,
            closeOnClickModal: false
          }
        );
        this.revertSplitLoading = true;
        const res = await http.post('/api/OutboundBatchPicking/cancel-split-chain', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode,
          startBarcode: this.splitChainInfo.originalBarcode
        });
        if (res.status) {
          this.$message.success('取消拆包链成功');
          this.closeAllDialogs();
          await this.loadPalletData();
        } else {
          this.$message.error(res.message || '取消拆包链失败');
        }
      } catch (error) {
        if (error !== 'cancel') {
          this.$message.error('取消拆包链失败: ' + error.message);
        }
      } finally {
        this.revertSplitLoading = false;
      }
    },
    // æ£€æŸ¥æ¡ç æ˜¯å¦å·²è¢«åˆ†æ‹£
    hasPicked(barcode) {
      return this.pickedList.some(item => item.currentBarcode === barcode);
    },
    // æ ¼å¼åŒ–日期时间
    formatDateTime(dateTime) {
      if (!dateTime) return '';
      const date = new Date(dateTime);
      return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`;
    },
    // å…³é”®ä¿®å¤ï¼šæ–°å¢žå…³é—­æ‰€æœ‰å¼¹çª—的方法
    closeAllDialogs() {
      this.activeDialog = null;
      // ç¡®ä¿æ‰€æœ‰å¼¹çª—状态都被重置
// å…³é—­Vue弹窗
      this.showCustomSplitDialog = false;
      this.showRevertSplitDialog = false;
      this.showBatchReturnDialog = false;
      this.showEmptyPalletDialog = false;
      this.showSplitChainDialog = false;
      // å…³é—­æ‰‹åŠ¨å¼¹çª—
      if (this.manualDialog && this.manualDialog.parentNode) {
        this.manualDialog.parentNode.removeChild(this.manualDialog);
        this.manualDialog = null;
      }
      // ç§»é™¤æ‰€æœ‰ç´§æ€¥å¼¹çª—
      const emergencyDialogs = document.querySelectorAll('.emergency-fix');
      emergencyDialogs.forEach(dialog => {
        if (dialog.parentNode) {
          dialog.parentNode.removeChild(dialog);
        }
      });
    },
    // å›žåº“相关方法
    openBatchReturnDialog() {
      if (!this.scanData.palletCode) {
        this.$message.warning('请先扫描托盘码');
        return;
      }
      if (this.isOpeningDialog) return;
      this.isOpeningDialog = true;
      setTimeout(() => {
        this.closeAllDialogsImmediately();
        requestAnimationFrame(() => {
          this.showBatchReturnDialog = true;
          this.batchReturnForm.orderNo = this.scanData.orderNo;
          this.batchReturnForm.palletCode = this.scanData.palletCode;
          this.batchReturnForm.unpickedCount = this.summary.unpickedCount;
          this.batchReturnForm.unpickedQuantity = this.summary.unpickedQuantity;
          this.$nextTick(() => {
            this.isOpeningDialog = false;
          });
        });
      }, 0);
    },
    async handleBatchReturnConfirm() {
      this.batchReturnLoading = true;
      try {
        const res = await http.post('/api/BatchOutbound/batch-return-stock', {
        const res = await http.post('/api/OutboundBatchPicking/return-stock', {
          orderNo: this.scanData.orderNo,
          batchNo: this.currentBatchNo
          palletCode: this.scanData.palletCode
        });
        if (res.status) {
          this.$message.success('批次回库成功');
          this.showBatchReturnDialog = false;
          await this.loadBatchData();
          this.$message.success('回库成功');
          this.closeAllDialogs();
          await this.loadPalletData();
        } else {
          this.$message.error(res.message || '批次回库失败');
          this.$message.error(res.message || '回库失败');
        }
      } catch (error) {
        this.$message.error('批次回库失败');
        this.$message.error('回库失败');
      } finally {
        this.batchReturnLoading = false;
      }
    },
    // å–空箱方法
handleEmptyPallet() {
      if (this.isOpeningDialog) return;
      this.isOpeningDialog = true;
      setTimeout(() => {
        this.closeAllDialogsImmediately();
        requestAnimationFrame(() => {
          this.showEmptyPalletDialog = true;
          this.emptypalletOutForm.orderNo = this.scanData.orderNo;
          this.emptypalletOutForm.palletCode = '';
          this.$nextTick(() => {
            this.isOpeningDialog = false;
          });
        });
      }, 0);
    },
    async handleEmptyPalletConfirm() {
      this.emptypalletOutLoading = true;
      try {
        const res = await http.post('/api/BatchOutbound/remove-empty-pallet', this.emptypalletOutForm);
        const res = await http.post('/api/OutboundBatchPicking/remove-empty-pallet', {
          orderNo: this.emptypalletOutForm.orderNo,
          palletCode: this.emptypalletOutForm.palletCode
        });
        if (res.status) {
          this.$message.success('取走空箱成功');
          this.showEmptyPalletDialog = false;
          await this.loadBatchData();
          this.closeAllDialogs();
          await this.loadPalletData();
        } else {
          this.$message.error(res.message || '取走空箱失败');
        }
@@ -788,32 +1391,76 @@
      }
    },
    // å…¶ä»–原有方法...
    // æ•°æ®åŠ è½½æ–¹æ³•
    async loadPalletData() {
      if (!this.scanData.orderNo || !this.scanData.palletCode) return;
      try {
        await this.loadUnpickedList();
        await this.loadPickedList();
        await this.loadPalletStatus();
      } catch (error) {
        console.error('加载托盘数据失败:', error);
      }
    },
    async loadUnpickedList() {
      try {
        const res = await http.post('/api/OutboundBatchPicking/pallet-locks', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode
        });
        if (res.status) {
          this.unpickedList = (res.data || []).filter(item => item.canPick === true);
          this.summary.unpickedCount = this.unpickedList.length;
          this.summary.unpickedQuantity = this.unpickedList.reduce((sum, item) => sum + (item.remainQuantity || 0), 0);
        }
      } catch (error) {
        this.$message.error('加载未拣选列表失败');
      }
    },
    async loadPickedList() {
      try {
        const res = await http.post('/api/OutboundBatchPicking/pallet-picked-list', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode
        });
        if (res.status) {
          this.pickedList = res.data.map(item => ({
            ...item,
            currentBarcode: item.barcode
          }));
          this.summary.pickedCount = this.pickedList.length;
        }
      } catch (error) {
        this.$message.error('加载已拣选列表失败');
      }
    },
    async loadPalletStatus() {
      try {
        const res = await http.post('/api/OutboundBatchPicking/pallet-status', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode
        });
        if (res.status) {
          this.palletStatus = res.data.statusText || '未知';
        }
      } catch (error) {
        this.palletStatus = '未知';
      }
    },
    // æ‰«ç ç›¸å…³æ–¹æ³•
    onPalletScan() {
      this.scanData.palletCode = this.scanData.palletCode.replace(/\n/g, '').trim();
      if (!this.scanData.palletCode) return;
      
      this.loadActiveBatch();
      this.loadPalletData();
      this.$nextTick(() => {
        this.$refs.barcodeInput.focus();
      });
    },
    async loadActiveBatch() {
      try {
        const res = await http.post('/api/BatchOutbound/active-batch', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode
        });
        if (res.status && res.data) {
          this.currentBatchNo = res.data.batchNo;
          this.scanData.batchNo = res.data.batchNo;
          this.selectedBatchNo = res.data.batchNo;
          await this.loadBatchData();
        }
      } catch (error) {
        console.log('获取活跃批次失败,可能托盘没有关联批次');
      }
    },
    onBarcodeScan() {
@@ -853,9 +1500,8 @@
        try {
          for (const row of this.selectedPickedRows) {
            try {
              const res = await http.post('/api/BatchOutbound/cancel-picking', {
              const res = await http.post('/api/OutboundBatchPicking/cancel-picking', {
                orderNo: this.scanData.orderNo,
                batchNo: this.currentBatchNo,
                palletCode: this.scanData.palletCode,
                barcode: row.currentBarcode
              });
@@ -867,7 +1513,7 @@
            }
          }        
          this.$message.success('批量取消完成');
          await this.loadBatchData();
          await this.loadPalletData();
          this.selectedPickedRows = [];
        } catch (error) {
          this.$message.error('批量取消操作失败');
@@ -888,34 +1534,46 @@
      this.resetSplitForm();
    },
    openRevertSplitDialog() {
      this.showRevertSplitDialog = true;
      this.revertSplitForm.newBarcode = '';
openRevertSplitDialog() {
      if (this.isOpeningDialog) return;
      this.isOpeningDialog = true;
      setTimeout(() => {
        this.closeAllDialogsImmediately();
        requestAnimationFrame(() => {
          this.showRevertSplitDialog = true;
          this.revertSplitForm.newBarcode = '';
          this.$nextTick(() => {
            this.isOpeningDialog = false;
          });
        });
      }, 0);
    },
    closeRevertSplitDialog() {
  closeRevertSplitDialog() {
      this.showRevertSplitDialog = false;
      this.revertSplitForm.newBarcode = '';
    },
    closeBatchReturnDialog() {
 closeBatchReturnDialog() {
      this.showBatchReturnDialog = false;
    },
    openEmptyPalletDialog() {
      this.showEmptyPalletDialog = true;
      this.emptypalletOutForm.orderNo = this.scanData.orderNo;
      this.emptypalletOutForm.palletCode = '';
    },
    closeEmptyPalletDialog() {
      this.showEmptyPalletDialog = false;
      this.emptypalletOutForm.palletCode = '';
    },
    onEmptyPalletScan() {
      if (!this.emptypalletOutForm.palletCode) return;
      this.emptypalletOutForm.palletCode = this.emptypalletOutForm.palletCode.replace(/\n/g, '').trim();
    },
     closeEmptyPalletDialog() {
      this.showEmptyPalletDialog = false;
      this.emptypalletOutForm.palletCode = '';
    },
    parentcall() {
      // æ‰“印回调
    }
  }
})
@@ -924,26 +1582,6 @@
<style scoped>
.OutboundPicking-container {
  padding: 20px;
}
.batch-operations {
  margin-bottom: 15px;
}
.batch-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
.batch-summary-area {
  margin-bottom: 15px;
}
.batch-summary-info {
  display: flex;
  gap: 15px;
  flex-wrap: wrap;
}
.scanner-form {
@@ -976,7 +1614,7 @@
  color: #909399;
}
/* è‡ªå®šä¹‰å¼¹çª—样式 */
/* è‡ªå®šä¹‰å¼¹çª—样式 - å…³é”®ä¿®å¤ */
.custom-dialog-overlay {
  position: fixed;
  top: 0;
@@ -987,7 +1625,7 @@
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  z-index: 9999; /* æé«˜z-index确保在最上层 */
}
.custom-dialog-wrapper {
@@ -1063,4 +1701,140 @@
    width: 100%;
  }
}
/* åŽŸæœ‰çš„æ ·å¼ä¿æŒä¸å˜ */
.OutboundPicking-container {
  padding: 20px;
}
.scanner-form {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}
.scanner-form .el-input {
  width: 200px;
}
.summary-info {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}
.table-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
  padding: 0 10px;
}
.selection-count {
  font-size: 12px;
  color: #909399;
}
/* åŽŸæœ‰çš„è‡ªå®šä¹‰å¼¹çª—æ ·å¼ */
.custom-dialog-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
}
.custom-dialog-wrapper {
  position: relative;
  z-index: 2001;
}
.custom-dialog {
  background: white;
  border-radius: 4px;
  width: 500px;
  max-width: 90vw;
  max-height: 90vh;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  overflow: auto;
}
.custom-dialog-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 20px 10px;
  border-bottom: 1px solid #ebeef5;
}
.custom-dialog-header h3 {
  margin: 0;
  color: #303133;
}
.close-button {
  font-size: 18px;
  color: #909399;
  padding: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.close-button:hover {
  color: #409EFF;
  background-color: transparent;
}
.custom-dialog-body {
  padding: 20px;
}
.custom-dialog-footer {
  padding: 10px 20px 20px;
  text-align: right;
  border-top: 1px solid #ebeef5;
}
.custom-dialog-footer .el-button {
  margin-left: 10px;
}
@media (max-width: 768px) {
  .custom-dialog {
    width: 95vw;
    margin: 10px;
  }
  .scanner-form {
    flex-direction: column;
    align-items: stretch;
  }
  .scanner-form .el-input {
    width: 100%;
  }
}
/* æ–°å¢žï¼šæ‰‹åŠ¨å¼¹çª—çš„æŒ‰é’®æ‚¬åœæ•ˆæžœ */
:deep(button) {
  transition: all 0.3s;
}
:deep(button:hover) {
  opacity: 0.8;
}
:deep(button:active) {
  opacity: 0.6;
}
</style>