1
647556386
2025-11-30 8639f19c82f6e263654db44286256bb8d028d2c2
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/BatchPickingConfirm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1278 @@
<template>
  <div class="OutboundPicking-container">
    <div class="page-header">
      <el-page-header @back="goBack">
        <template #content>
          <span class="title">出库拣选确认 - {{ this.$route.query.orderNo }}</span>
        </template>
      </el-page-header>
    </div>
    <!-- æ‰«ç åŒºåŸŸ -->
    <div class="scanner-area">
      <el-card>
        <div class="scanner-form">
          <el-input
            ref="palletInput"
            v-model="scanData.palletCode"
            placeholder="扫描托盘码"
            @change="onPalletScan"
            @keyup.enter.native="onPalletScan">
          </el-input>
          <el-input
            ref="barcodeInput"
            v-model="scanData.barcode"
            placeholder="扫描物料条码"
            @change="onBarcodeScan"
            @keyup.enter.native="onBarcodeScan">
          </el-input>
          <el-button type="success" @click="confirmPicking">确认拣选</el-button>
          <el-button type="warning" @click="openSplitDialog">拆包</el-button>
          <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="summary-area">
      <el-card>
        <div class="summary-info">
          <el-tag type="warning">未拣选条数: {{summary.unpickedCount}}</el-tag>
          <el-tag type="danger">未拣选数量: {{summary.unpickedQuantity}}</el-tag>
          <el-tag type="info">托盘状态: {{palletStatus}}</el-tag>
        </div>
      </el-card>
    </div>
    <!-- æ•°æ®åˆ—表 -->
    <div class="content-area">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-card header="未拣选列表">
            <el-table :data="unpickedList" border height="440">
              <el-table-column prop="materielCode" label="物料编码" width="120"></el-table-column>
              <el-table-column prop="assignQuantity" label="分配数量" width="100"></el-table-column>
              <el-table-column prop="pickedQty" label="已拣数量" width="100"></el-table-column>
              <el-table-column prop="remainQuantity" 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>
        </el-col>
        <el-col :span="12">
          <el-card header="已拣选列表">
            <div class="table-actions">
              <el-button
                size="mini"
                type="danger"
                :disabled="selectedPickedRows.length === 0"
                @click="batchCancelSelected">
                å–消拣选
              </el-button>
              <span class="selection-count">已选择 {{selectedPickedRows.length}} é¡¹</span>
            </div>
            <el-table
              :data="pickedList"
              border
              height="400"
              style="width: 100%"
              @selection-change="handlePickedSelectionChange">
              <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="currentBarcode" label="条码"></el-table-column>
            </el-table>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- æ‹†åŒ…弹窗 -->
    <div v-if="showCustomSplitDialog" 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="closeCustomSplitDialog" class="close-button">X</el-button>
          </div>
          <div class="custom-dialog-body">
            <el-form :model="splitForm" :rules="splitFormRules" ref="splitFormRef" label-width="100px">
              <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.palletCode" disabled></el-input>
              </el-form-item>
              <el-form-item label="原条码" prop="originalBarcode">
                <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>
              </el-form-item>
              <el-form-item label="剩余数量">
                <el-input v-model="splitForm.maxQuantity" disabled></el-input>
              </el-form-item>
              <el-form-item label="拆包数量" prop="splitQuantity">
                <el-input-number
                  v-model="splitForm.splitQuantity"
                  :min="1"
                  :max="splitForm.maxQuantity"
                  :precision="2"
                  :step="1"
                  style="width: 100%">
                </el-input-number>
              </el-form-item>
            </el-form>
          </div>
          <div class="custom-dialog-footer">
            <el-button @click="closeCustomSplitDialog">取消</el-button>
            <el-button type="primary" @click="handleSplitPackage" :loading="splitLoading">确认拆包</el-button>
          </div>
        </div>
      </div>
    </div>
    <!-- æ’¤é”€æ‹†åŒ…弹窗 -->
    <div v-if="showRevertSplitDialog" 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="closeRevertSplitDialog" class="close-button">×</el-button>
          </div>
          <div class="custom-dialog-body">
            <el-form :model="revertSplitForm" :rules="revertSplitFormRules" ref="revertSplitFormRef" label-width="100px">
              <el-form-item label="新条码" prop="newBarcode">
                <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>
            <el-button type="primary" @click="handleRevertSplit" :loading="revertSplitLoading">确认撤销</el-button>
          </div>
        </div>
      </div>
    </div>
    <!-- æ‹†åŒ…链信息弹窗 -->
<div v-if="showSplitChainDialog" class="custom-dialog-overlay">
  <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 class="custom-dialog-wrapper">
        <div class="custom-dialog">
          <div class="custom-dialog-header">
            <h3>托盘回库</h3>
            <el-button type="text" @click="closeBatchReturnDialog" class="close-button">×</el-button>
          </div>
          <div class="custom-dialog-body">
            <el-form :model="batchReturnForm" :rules="batchReturnFormRules" ref="batchReturnFormRef" label-width="100px">
              <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.palletCode" disabled></el-input>
              </el-form-item>
              <el-form-item label="未拣选数量">
                <el-input v-model="batchReturnForm.unpickedQuantity" disabled></el-input>
              </el-form-item>
              <el-form-item label="未拣选条数">
                <el-input v-model="batchReturnForm.unpickedCount" disabled></el-input>
              </el-form-item>
            </el-form>
          </div>
          <div class="custom-dialog-footer">
            <el-button @click="closeBatchReturnDialog">取消</el-button>
            <el-button type="primary" @click="handleBatchReturnConfirm" :loading="batchReturnLoading">确认回库</el-button>
          </div>
        </div>
      </div>
    </div>
    <!-- å–走空箱弹窗 -->
    <div v-if="showEmptyPalletDialog" 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="closeEmptyPalletDialog" class="close-button">×</el-button>
          </div>
          <div class="custom-dialog-body">
            <el-form :model="emptypalletOutForm" :rules="emptypalletOutFormRules" ref="emptypalletOutFormRef" label-width="100px">
              <el-form-item label="订单编号">
                <el-input v-model="emptypalletOutForm.orderNo" disabled></el-input>
              </el-form-item>
              <el-form-item label="托盘编号" prop="palletCode">
                <el-input
                  v-model="emptypalletOutForm.palletCode"
                  placeholder="扫描托盘码"
                  @keyup.enter.native="onEmptyPalletScan"
                  @change="onEmptyPalletScan"
                  clearable>
                </el-input>
              </el-form-item>
            </el-form>
          </div>
          <div class="custom-dialog-footer">
            <el-button @click="closeEmptyPalletDialog">取消</el-button>
            <el-button type="primary" @click="handleEmptyPalletConfirm" :loading="emptypalletOutLoading">确认取走空箱</el-button>
          </div>
        </div>
      </div>
    </div>
    <print-view ref="childs" @parentcall="parentcall"></print-view>
  </div>
</template>
<script>
import http from '@/api/http.js'
import { ref, 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() {
    return {
      scanData: {
        orderNo: '',
        palletCode: '',
        barcode: ''
      },
      unpickedList: [],
      pickedList: [],
      selectedPickedRows: [],
      summary: {
        unpickedCount: 0,
        unpickedQuantity: 0,
        pickedCount: 0
      },
      palletStatus: '未知',
      // å¼¹çª—状态
      showCustomSplitDialog: false,
      showRevertSplitDialog: false,
      showBatchReturnDialog: false,
      showEmptyPalletDialog: false,
      showSplitChainDialog: false, // æ–°å¢žï¼šæ‹†åŒ…链信息弹窗
      // åŠ è½½çŠ¶æ€
      splitLoading: false,
      revertSplitLoading: false,
      batchReturnLoading: false,
      emptypalletOutLoading: false,
      splitChainLoading: false, // æ–°å¢žï¼šæ‹†åŒ…链加载状态
      // è¡¨å•数据
      splitForm: {
        orderNo: '',
        palletCode: '',
        originalBarcode: '',
        materielCode: '',
        splitQuantity: 0,
        maxQuantity: 0
      },
      revertSplitForm: {
        newBarcode: ''
      },
      batchReturnForm: {
        orderNo: '',
        palletCode: '',
        unpickedCount: 0,
        unpickedQuantity: 0
      },
      emptypalletOutForm: {
        orderNo: '',
        palletCode: ''
      },
      // æ–°å¢žï¼šæ‹†åŒ…链相关数据
      splitChainInfo: {
        originalBarcode: '',
        totalSplitTimes: 0,
        splitChain: []
      },
      // éªŒè¯è§„则
      splitFormRules: {
        originalBarcode: [
          { required: true, message: '请输入原条码', 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
    }
  },
  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.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();
    });
  },
  methods: {
    goBack(){
      this.$router.back()
    },
    // åˆ†æ‹£ç›¸å…³æ–¹æ³•
    async confirmPicking() {
      if (this.isProcessing) return;
      if (!this.scanData.orderNo || !this.scanData.palletCode || !this.scanData.barcode) {
        this.$message.warning('请先扫描托盘码和物料条码');
        this.focusBarcodeInput();
        return;
      }
      this.isProcessing = true;
      try {
        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.loadPalletData();
          if(res.data && res.data.splitResults && res.data.splitResults.length>0){
            this.$refs.childs.open(res.data.splitResults);
          }
          this.$nextTick(() => {
            this.$refs.barcodeInput.focus();
          });
        } else {
          this.$message.error(res.message || '拣选确认失败');
          this.focusBarcodeInput(true);
        }
      } catch (error) {
        this.$message.error('拣选确认失败: ' + (error.message || '网络错误'));
        this.focusBarcodeInput(true);
      } finally {
        this.isProcessing = false;
      }
    },
    // æ‹†åŒ…相关方法
    openSplitDialog() {
      if (!this.scanData.palletCode) {
        this.$message.warning('请先扫描托盘码');
        return;
      }
      this.showCustomSplitDialog = true;
      this.resetSplitForm();
      this.splitForm.orderNo = this.scanData.orderNo;
      this.splitForm.palletCode = this.scanData.palletCode;
    },
    async onSplitBarcodeScan() {
      if (!this.splitForm.originalBarcode) return;
      this.splitForm.originalBarcode = this.splitForm.originalBarcode.replace(/\n/g, '').trim();
      try {
        const res = await http.post('/api/OutboundBatchPicking/split-package-info', {
          orderNo: this.splitForm.orderNo,
          palletCode: this.splitForm.palletCode,
          barcode: this.splitForm.originalBarcode
        });
        if (res.status) {
          this.splitForm.materielCode = res.data.materielCode;
          this.splitForm.maxQuantity = res.data.remainQuantity;
          this.splitForm.splitQuantity = Math.min(1, this.splitForm.maxQuantity);
        } else {
          this.$message.error(res.message || '获取拆包信息失败');
        }
      } catch (error) {
        this.$message.error('获取拆包信息失败');
      }
    },
    async handleSplitPackage() {
      if (this.$refs.splitFormRef) {
        this.$refs.splitFormRef.validate(async (valid) => {
          if (valid) {
            this.splitLoading = true;
            try {
              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.loadPalletData();
              } else {
                this.$message.error(res.message || '拆包失败');
              }
            } catch (error) {
              this.$message.error('拆包失败');
            } finally {
              this.splitLoading = false;
            }
          }
        });
      }
    },
// åœ¨æ‹†åŒ…弹窗中查看拆包链
async viewSplitChainFromSplit(barcode) {
  if (!barcode) {
    this.$message.warning('请先输入条码');
    return;
  }
  // å…ˆå…³é—­æ‹†åŒ…弹窗
  this.closeCustomSplitDialog();
  await this.$nextTick();
  // ç„¶åŽæ‰“开拆包链信息弹窗
  await this.viewSplitChain(barcode);
},
    // æ’¤é”€æ‹†åŒ…
    async onRevertSplitBarcodeScan() {
      if (!this.revertSplitForm.newBarcode) return;
      this.revertSplitForm.newBarcode = this.revertSplitForm.newBarcode.replace(/\n/g, '').trim();
      // æ–°å¢žï¼šæ‰«æåŽè‡ªåŠ¨æ˜¾ç¤ºæ‹†åŒ…é“¾ä¿¡æ¯
      await this.viewSplitChain(this.revertSplitForm.newBarcode);
    },
    async handleRevertSplit() {
      if (this.$refs.revertSplitFormRef) {
        this.$refs.revertSplitFormRef.validate(async (valid) => {
          if (valid) {
            this.revertSplitLoading = true;
            try {
              const res = await http.post('/api/OutboundBatchPicking/cancel-split', {
                orderNo: this.scanData.orderNo,
                palletCode: this.scanData.palletCode,
                newBarcode: this.revertSplitForm.newBarcode
              });
              if (res.status) {
                this.$message.success('撤销拆包成功');
                this.showRevertSplitDialog = false;
                await this.loadPalletData();
              } else {
                this.$message.error(res.message || '撤销拆包失败');
              }
            } catch (error) {
              this.$message.error('撤销拆包失败');
            } finally {
              this.revertSplitLoading = false;
            }
          }
        });
      }
    },
// æŸ¥æ‰¾å®Œæ•´æ‹†åŒ…链(从根条码开始)
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.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;
      // æ˜¾ç¤ºæç¤ºä¿¡æ¯ï¼Œå‘Šè¯‰ç”¨æˆ·è¿™æ˜¯ä»€ä¹ˆç±»åž‹çš„æ‹†åŒ…链
      let chainType = "当前条码的拆包链";
      if (this.splitChainInfo.chainType === 'root') {
        chainType = "完整拆包链(从原始条码开始)";
      } else if (this.splitChainInfo.chainType === 'branch') {
        chainType = "分支拆包链";
      }
      this.$message.info(`已加载${chainType},共${this.splitChainInfo.totalSplitTimes}次拆包`);
      this.showSplitChainDialog = true;
    } else {
      this.$message.error(res.message || '获取拆包链信息失败');
    }
  } catch (error) {
    this.$message.error('获取拆包链信息失败');
  } finally {
    this.splitChainLoading = false;
  }
},
    // å…³é—­æ‹†åŒ…链信息弹窗
    closeSplitChainDialog() {
      this.showSplitChainDialog = false;
    },
    // åœ¨æ’¤é”€æ‹†åŒ…弹窗中查看拆包链
async viewSplitChainFromRevert(barcode) {
  if (!barcode) {
    this.$message.warning('请先输入条码');
    return;
  }
  // å…ˆå…³é—­æ’¤é”€æ‹†åŒ…弹窗
  this.closeRevertSplitDialog();
  await this.$nextTick();
  // ç„¶åŽæ‰“开拆包链信息弹窗
  await this.viewSplitChain(barcode);
},
// å¿«é€Ÿé‡æ–°æ‰“开拆包链弹窗
async quickReopenSplitChainDialog(barcode) {
  if (!barcode) return;
  this.showSplitChainDialog = true;
  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;
    }
  } catch (error) {
    console.error('重新加载拆包链信息失败:', error);
  } finally {
    this.splitChainLoading = false;
  }
},
    // å–消单个拆包记录
async cancelSingleSplit(newBarcode) {
  // å…ˆè®°å½•当前信息,然后关闭弹窗
  const originalBarcode = this.splitChainInfo.originalBarcode;
  this.closeSplitChainDialog();
  await this.$nextTick();
  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();
      // é‡æ–°æ‰“开弹窗显示更新后的状态
      await this.viewSplitChain(originalBarcode);
    } else {
      this.$message.error(res.message || '取消拆包失败');
      await this.viewSplitChain(originalBarcode);
    }
  } catch (error) {
    if (error === 'cancel') {
      // ç”¨æˆ·å–消后重新打开弹窗
      await this.viewSplitChain(originalBarcode);
    } else {
      this.$message.error('取消拆包失败');
      await this.viewSplitChain(originalBarcode);
    }
  } finally {
    this.revertSplitLoading = false;
  }
},
// å–消整个拆包链
async cancelWholeSplitChain() {
  // å…ˆè®°å½•当前拆包链信息,然后关闭弹窗
  const originalBarcode = this.splitChainInfo.originalBarcode;
  this.closeSplitChainDialog();
  // ç»™ä¸€ç‚¹æ—¶é—´è®©å¼¹çª—完全关闭
  await this.$nextTick();
  try {
    // çŽ°åœ¨æ˜¾ç¤ºç¡®è®¤å¯¹è¯æ¡†ï¼Œç¡®ä¿å®ƒåœ¨æœ€å‰é¢
    await this.$confirm(
      `确定要取消整个拆包链吗?\n这将取消从条码 ${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: originalBarcode
    });
    console.log('取消拆包链响应:', res);
    if (res.status) {
      this.$message.success('取消拆包链成功');
      await this.loadPalletData();
      // å¯é€‰ï¼šé‡æ–°æ‰“开拆包链信息弹窗显示更新后的状态
      // await this.viewSplitChain(originalBarcode);
    } else {
      this.$message.error(res.message || '取消拆包链失败');
      // å¤±è´¥åŽé‡æ–°æ‰“开弹窗
      await this.viewSplitChain(originalBarcode);
    }
  } catch (error) {
    // ç”¨æˆ·å–消操作
    if (error === 'cancel') {
      console.log('用户取消了拆包链操作');
      // ç”¨æˆ·å–消后重新打开弹窗
      await this.viewSplitChain(originalBarcode);
    } else {
      console.error('取消拆包链错误:', error);
      this.$message.error('取消拆包链失败: ' + error.message);
      // å‡ºé”™åŽé‡æ–°æ‰“开弹窗
      await this.viewSplitChain(originalBarcode);
    }
  } 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')}`;
    },
    // å›žåº“相关方法
    openBatchReturnDialog() {
      if (!this.scanData.palletCode) {
        this.$message.warning('请先扫描托盘码');
        return;
      }
      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;
    },
    async handleBatchReturnConfirm() {
      this.batchReturnLoading = true;
      try {
        const res = await http.post('/api/OutboundBatchPicking/return-stock', {
          orderNo: this.scanData.orderNo,
          palletCode: this.scanData.palletCode
        });
        if (res.status) {
          this.$message.success('回库成功');
          this.showBatchReturnDialog = false;
          await this.loadPalletData();
        } else {
          this.$message.error(res.message || '回库失败');
        }
      } catch (error) {
        this.$message.error('回库失败');
      } finally {
        this.batchReturnLoading = false;
      }
    },
    // å–空箱方法
    handleEmptyPallet() {
      this.showEmptyPalletDialog = true;
      this.emptypalletOutForm.orderNo = this.scanData.orderNo;
      this.emptypalletOutForm.palletCode = '';
    },
    async handleEmptyPalletConfirm() {
      this.emptypalletOutLoading = true;
      try {
        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.loadPalletData();
        } else {
          this.$message.error(res.message || '取走空箱失败');
        }
      } catch (error) {
        this.$message.error('取走空箱失败');
      } finally {
        this.emptypalletOutLoading = false;
      }
    },
    // æ•°æ®åŠ è½½æ–¹æ³•
    async loadPalletData() {
      if (!this.scanData.orderNo || !this.scanData.palletCode) return;
      await this.loadUnpickedList();
      await this.loadPickedList();
      await this.loadPalletStatus();
    },
    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 || [];
          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 || [];
          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.loadPalletData();
      this.$nextTick(() => {
        this.$refs.barcodeInput.focus();
      });
    },
    onBarcodeScan() {
      this.scanData.barcode = this.scanData.barcode.replace(/\n/g, '').trim();
      if (!this.scanData.barcode) return;
      this.confirmPicking();
    },
    focusBarcodeInput(selectText = false) {
      this.$nextTick(() => {
        const input = this.$refs.barcodeInput;
        if (input && input.$el && input.$el.querySelector('input')) {
          const inputEl = input.$el.querySelector('input');
          inputEl.focus();
          if (selectText) {
            inputEl.select();
          }
        }
      });
    },
    handlePickedSelectionChange(selection) {
      this.selectedPickedRows = selection;
    },
    async batchCancelSelected() {
      if (this.selectedPickedRows.length === 0) {
        this.$message.warning('请先选择要取消的项');
        return;
      }
      this.$confirm(`确定要取消选中的 ${this.selectedPickedRows.length} é¡¹å—?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        try {
          for (const row of this.selectedPickedRows) {
            try {
              const res = await http.post('/api/OutboundBatchPicking/cancel-picking', {
                orderNo: this.scanData.orderNo,
                palletCode: this.scanData.palletCode,
                barcode: row.currentBarcode
              });
              if (!res.status) {
                this.$message.warning(`取消拣选失败: ${row.currentBarcode} - ${res.message}`);
              }
            } catch (error) {
              this.$message.warning(`取消拣选失败: ${row.currentBarcode} - ${error.message}`);
            }
          }
          this.$message.success('批量取消完成');
          await this.loadPalletData();
          this.selectedPickedRows = [];
        } catch (error) {
          this.$message.error('批量取消操作失败');
        }
      });
    },
    // é‡ç½®æ–¹æ³•
    resetSplitForm() {
      this.splitForm.originalBarcode = '';
      this.splitForm.materielCode = '';
      this.splitForm.splitQuantity = 0;
      this.splitForm.maxQuantity = 0;
    },
    closeCustomSplitDialog() {
      this.showCustomSplitDialog = false;
      this.resetSplitForm();
    },
    openRevertSplitDialog() {
      this.showRevertSplitDialog = true;
      this.revertSplitForm.newBarcode = '';
    },
    closeRevertSplitDialog() {
      this.showRevertSplitDialog = false;
      this.revertSplitForm.newBarcode = '';
    },
    closeBatchReturnDialog() {
      this.showBatchReturnDialog = false;
    },
    onEmptyPalletScan() {
      if (!this.emptypalletOutForm.palletCode) return;
      this.emptypalletOutForm.palletCode = this.emptypalletOutForm.palletCode.replace(/\n/g, '').trim();
    },
    closeEmptyPalletDialog() {
      this.showEmptyPalletDialog = false;
      this.emptypalletOutForm.palletCode = '';
    },
    parentcall() {
      // æ‰“印回调
    }
  }
})
</script>
<style scoped>
.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;
}
/* è‡ªå®šä¹‰å¼¹çª—样式 */
:deep(.el-message-box) {
  z-index: 10010 !important;
}
:deep(.el-overlay) {
  z-index: 10009 !important;
}
:deep(.el-message) {
  z-index: 10011 !important;
}
.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; /* ä¿æŒä¸€ä¸ªåˆç†çš„ z-index */
}
.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;
  }
  /* ç¡®ä¿ç¡®è®¤å¯¹è¯æ¡†åœ¨æœ€å‰é¢ */
.el-message-box__wrapper {
  z-index: 10001 !important;
}
.el-message {
  z-index: 10002 !important;
}
  .scanner-form .el-input {
    width: 100%;
  }
}
</style>