pan
2025-11-13 80dbe95a364ec753195ab9087c1eca1f5a6fa27c
提交
已添加1个文件
已修改2个文件
312 ■■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/src/views/outbound/Picking.vue 312 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-shm 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-shm 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/Picking.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,312 @@
<template>
  <div class="picking-container">
    <!-- å‡ºåº“单列表 -->
    <div class="card mb-4" v-for="order in pickingOrders" :key="order.outStockOrderId">
      <div class="card-header bg-light d-flex justify-content-between align-items-center">
        <div>
          <h5 class="mb-0">出库单: {{ order.orderNumber }}</h5>
          <small class="text-muted">
            éœ€æ±‚: {{ order.totalRequiredQuantity }} |
            å·²é”å®š: {{ order.totalLockedQuantity }}
          </small>
        </div>
        <button @click="confirmOrder(order)" class="btn btn-primary btn-sm"
                :disabled="!canConfirmOrder(order)">
          <i class="fas fa-check"></i> ç¡®è®¤æ•´å•出库
        </button>
      </div>
      <div class="card-body p-0">
        <div class="table-responsive">
          <table class="table table-hover mb-0">
            <thead>
              <tr>
                <th width="5%">#</th>
                <th>产品名称</th>
                <th>批号/条码</th>
                <th>出库数量</th>
                <th>库存数量</th>
                <th>库位</th>
                <th width="20%">操作</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="(item, index) in order.pickingItems" :key="item.outStockLockId"
                  :class="{
                    'table-success': item.status === 'Confirmed',
                    'table-warning': needsSplit(item)
                  }">
                <td>{{ index + 1 }}</td>
                <td>{{ item.productName }}</td>
                <td>
                  <div>
                    <strong>{{ item.batchNumber }}</strong>
                    <br>
                    <small class="text-muted">{{ item.barCode }}</small>
                  </div>
                </td>
                <td>
                  <span class="font-weight-bold text-primary">
                    {{ item.outStockQuantity }}
                  </span>
                  <small class="text-muted ml-1">{{ item.packageUnit }}</small>
                </td>
                <td>
                  <span :class="{
                    'text-success': item.isWholePackage,
                    'text-warning': needsSplit(item)
                  }">
                    {{ item.stockQuantity }} {{ item.packageUnit }}
                  </span>
                </td>
                <td>{{ item.location }}</td>
                <td>
                  <!-- æ•´åŒ…直接确认 -->
                  <button v-if="item.isWholePackage && item.status === 'Pending'"
                          @click="confirmWholePicking(item)"
                          class="btn btn-success btn-sm"
                          :disabled="loading">
                    <i class="fas fa-check"></i> ç¡®è®¤
                  </button>
                  <!-- éœ€è¦æ‹†åŒ…的情况 -->
                  <div v-else-if="needsSplit(item) && item.status === 'Pending'">
                    <button @click="showSplitModal(item)"
                            class="btn btn-warning btn-sm mb-1"
                            :disabled="loading">
                      <i class="fas fa-cube"></i> æ‹†åŒ…
                    </button>
                    <div class="small text-muted">
                      éœ€æ‹†åŒ…: {{ item.outStockQuantity }}/{{ item.stockQuantity }}
                    </div>
                  </div>
                  <!-- å·²ç¡®è®¤çŠ¶æ€ -->
                  <span v-else-if="item.status === 'Confirmed'" class="text-success">
                    <i class="fas fa-check-circle"></i> å·²ç¡®è®¤
                  </span>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
    <!-- æ‹†åŒ…模态框 -->
    <div class="modal fade" id="splitModal" tabindex="-1">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title">拆包操作</h5>
            <button type="button" class="close" data-dismiss="modal">
              <span>&times;</span>
            </button>
          </div>
          <div class="modal-body">
            <div v-if="currentSplitItem">
              <div class="form-group">
                <label>产品名称</label>
                <input type="text" class="form-control" :value="currentSplitItem.productName" readonly>
              </div>
              <div class="form-group">
                <label>原条码</label>
                <input type="text" class="form-control" :value="currentSplitItem.barCode" readonly>
              </div>
              <div class="row">
                <div class="col-6">
                  <div class="form-group">
                    <label>原库存数量</label>
                    <input type="text" class="form-control" :value="currentSplitItem.stockQuantity" readonly>
                  </div>
                </div>
                <div class="col-6">
                  <div class="form-group">
                    <label>出库数量</label>
                    <input type="text" class="form-control" :value="currentSplitItem.outStockQuantity" readonly>
                  </div>
                </div>
              </div>
              <div class="form-group">
                <label>拆包数量</label>
                <input type="number" class="form-control" v-model="splitQuantity"
                       :max="currentSplitItem.stockQuantity - 0.01" step="0.01" min="0.01">
                <small class="form-text text-muted">
                  æ‹†å‡ºæ•°é‡: {{ splitQuantity }} |
                  å‰©ä½™æ•°é‡: {{ (currentSplitItem.stockQuantity - splitQuantity).toFixed(2) }}
                </small>
              </div>
            </div>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
            <button type="button" class="btn btn-warning" @click="confirmSplit"
                    :disabled="!splitQuantity || splitQuantity <= 0">
              <i class="fas fa-cube"></i> ç¡®è®¤æ‹†åŒ…
            </button>
          </div>
        </div>
      </div>
    </div>
    <!-- æ“ä½œæç¤º -->
    <div v-if="message" :class="['alert', messageType === 'success' ? 'alert-success' : 'alert-danger', 'mt-3']">
      {{ message }}
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      pickingOrders: [],
      loading: false,
      message: '',
      messageType: 'success',
      currentSplitItem: null,
      splitQuantity: 0
    }
  },
  mounted() {
    this.loadPickingList();
    // åˆå§‹åŒ–模态框
    $('#splitModal').on('hidden.bs.modal', () => {
      this.currentSplitItem = null;
      this.splitQuantity = 0;
    });
  },
  methods: {
    async loadPickingList() {
      try {
        this.loading = true;
        const response = await this.$http.get('/Picking/PickingList');
        this.pickingOrders = response.data;
      } catch (error) {
        this.showMessage('加载拣选列表失败', 'error');
      } finally {
        this.loading = false;
      }
    },
    // åˆ¤æ–­æ˜¯å¦éœ€è¦æ‹†åŒ…
    needsSplit(item) {
      return !item.isWholePackage && item.outStockQuantity <= item.stockQuantity;
    },
    // æ•´åŒ…确认
    async confirmWholePicking(item) {
      try {
        this.loading = true;
        const response = await this.$http.post('/Picking/ConfirmWholePicking', {
          outStockLockId: item.outStockLockId
        });
        if (response.data.success) {
          this.showMessage(response.data.message, 'success');
          this.loadPickingList();
        } else {
          this.showMessage(response.data.message, 'error');
        }
      } catch (error) {
        this.showMessage('操作失败', 'error');
      } finally {
        this.loading = false;
      }
    },
    // æ˜¾ç¤ºæ‹†åŒ…模态框
    showSplitModal(item) {
      this.currentSplitItem = item;
      this.splitQuantity = item.outStockQuantity; // é»˜è®¤æ‹†å‡ºæ•°é‡ä¸ºå‡ºåº“数量
      $('#splitModal').modal('show');
    },
    // ç¡®è®¤æ‹†åŒ…
    async confirmSplit() {
      if (!this.splitQuantity || this.splitQuantity <= 0) {
        this.showMessage('请输入有效的拆包数量', 'error');
        return;
      }
      try {
        this.loading = true;
        const response = await this.$http.post('/Picking/SplitPackage', {
          outStockLockId: this.currentSplitItem.outStockLockId,
          splitQuantity: this.splitQuantity
        });
        if (response.data.success) {
          this.showMessage(`拆包成功!新条码: ${response.data.data.newBarCode}`, 'success');
          $('#splitModal').modal('hide');
          this.loadPickingList();
        } else {
          this.showMessage(response.data.message, 'error');
        }
      } catch (error) {
        this.showMessage('拆包失败', 'error');
      } finally {
        this.loading = false;
      }
    },
    // ç¡®è®¤æ•´å•出库
    async confirmOrder(order) {
      if (!confirm(`确定要确认整个出库单 ${order.orderNumber} å—?`)) return;
      try {
        this.loading = true;
        const response = await this.$http.post('/Picking/ConfirmOutStockOrder', {
          outStockOrderId: order.outStockOrderId
        });
        if (response.data.success) {
          this.showMessage(response.data.message, 'success');
          this.loadPickingList();
        } else {
          this.showMessage(response.data.message, 'error');
        }
      } catch (error) {
        this.showMessage('操作失败', 'error');
      } finally {
        this.loading = false;
      }
    },
    // æ£€æŸ¥æ˜¯å¦å¯ä»¥ç¡®è®¤æ•´å•
    canConfirmOrder(order) {
      return order.pickingItems.every(item =>
        item.status === 'Pending' &&
        (item.isWholePackage || this.needsSplit(item))
      );
    },
    showMessage(msg, type) {
      this.message = msg;
      this.messageType = type;
      setTimeout(() => {
        this.message = '';
      }, 3000);
    }
  }
}
</script>
<style scoped>
.picking-container {
  max-width: 1400px;
  margin: 0 auto;
}
.table td {
  vertical-align: middle;
}
.btn-sm {
  min-width: 80px;
}
.badge {
  font-family: monospace;
}
</style>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-shm
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-shm
Binary files differ