Quellcode durchsuchen

增加入库、出库、调拨扫描页面

wangzhengguang vor 3 Monaten
Ursprung
Commit
ac94fb96ef

+ 2 - 1
.gitignore

@@ -1,2 +1,3 @@
 node_modules/**
-/dist/
+/dist/
+/.idea

+ 2 - 2
src/components/StockIn.vue

@@ -1,6 +1,6 @@
 <template>
   <van-nav-bar
-    :title="title" left-arrow left-text="返回" right-text="拍照" fixed placeholder @click-left="goBack()"
+    :title="title" left-arrow left-text="返回" right-text="扫码" fixed placeholder @click-left="goBack()"
     @click-right="goTakePhoto"
   />
   <van-search v-model="stockInSearch" placeholder="请输入搜索关键词" @search="searchStockIn" />
@@ -337,7 +337,7 @@ const clearFormData = () => {
 
 // 拍照
 const goTakePhoto = () => {
-  router.push('/stock-in-photo?warehouseId=' + warehouseId.value + '&warehouseName=' + warehouseName.value);
+  router.push('/stock-in-scan?warehouseId=' + warehouseId.value + '&warehouseName=' + warehouseName.value);
 };
 onMounted(() => {
   warehouseId.value = route.query.warehouseId || '';

+ 372 - 0
src/components/StockInScan.vue

@@ -0,0 +1,372 @@
+<template>
+  <van-nav-bar
+    :title="title" left-arrow left-text="返回" fixed placeholder right-text="返回菜单" @click-left="goBack()"
+    @click-right="goMenu"
+  />
+  <div class="content">
+    <div class="scan-btn">
+      <van-button block plain icon="orders-o" type="primary">
+        等待扫码数据
+      </van-button>
+    </div>
+    <van-form :scroll-to-error="true">
+      <van-field v-model="stockData.no" name="no" label="物料编号:" :readonly="true" />
+
+      <van-field v-model="stockData.name" name="name" label="物料名称:" :readonly="true" />
+
+      <van-field v-model="stockData.type" name="type" label="规格型号:" :readonly="true" />
+
+      <van-field v-model="stockData.batchNo" name="batchNo" label="批号:" placeholder="点击输入批号" type="textarea" rows="1" autosize />
+
+      <van-field v-model="stockData.num" name="num" label="生产数量:" placeholder="点击输入生产数量" />
+      <van-field name="inventoryPackaged" label="是否包装">
+        <template #input>
+          <van-switch v-model="stockData.inventoryPackaged" size="20px" />
+        </template>
+      </van-field>
+
+      <van-field v-model="stockData.carrierTypeName" name="carrierTypeName" label="托盘类型:" placeholder="点击选择托盘类型" is-link @click="showCarrierTypePicker = true" />
+
+      <van-field
+        v-model="stockData.transferName" is-link readonly name="transfer" label="中转区货位:"
+        placeholder="点击选择中转区货位" @click="isShowTransfer = true"
+      />
+      <van-field
+        v-model="stockData.idleName" is-link readonly name="warehouse" label="入库货位:" placeholder="点击选择入库货位"
+        @click="isShowIdle = true"
+      />
+      <div style="margin: 16px">
+        <van-button round block type="primary" @click="submit">
+          提交
+        </van-button>
+      </div>
+    </van-form>
+  </div>
+  <div>
+    <position-selector
+      ref="transferPositionSelector" v-model:show="isShowTransfer" position-type="transfer"
+      @confirm="onTransferPositionSelected"
+    />
+    <position-selector
+      ref="idlePositionSelector" v-model:show="isShowIdle" position-type="idle" type="stockInPhoto"
+      :is-default="true" @confirm="onIdlePositionSelected"
+    />
+  </div>
+  <van-popup v-model:show="showCarrierTypePicker" destroy-on-close round position="bottom">
+    <van-picker
+      :model-value="carrierType"
+      :columns="columns"
+      @cancel="showCarrierTypePicker = false"
+      @confirm="onConfirm"
+    />
+  </van-popup>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import PositionSelector from './PositionSelector.vue';
+import { processException } from '../common/Common.js';
+import { ajaxApiGet, ajaxApiPost } from '../common/utils.js';
+import { showSuccessToast, showFailToast, showConfirmDialog } from 'vant';
+import { showFullscreenLoading, hideFullscreenLoading } from '../common/loading.js';
+
+const route = useRoute();
+const router = useRouter();
+
+// WebSocket实例
+const ws = ref(null);
+
+// 状态
+const isShowTransfer = ref(false);
+const isShowIdle = ref(false);
+const transferPositionSelector = ref(null);
+const idlePositionSelector = ref(null);
+
+const stockData = ref({
+  no: '',
+  name: '',
+  type: '',
+  num: '',
+  batchNo: '',
+  transferId: '',
+  transferName: '',
+  transferNo: '',
+  idleId: '',
+  idleName: '',
+  idleNo: '',
+  inventoryPackaged: false,
+  carrierType:'',
+  carrierTypeName: '',
+});
+
+const warehouseId = ref('');
+const title  = ref('');
+
+onMounted(() => {
+  warehouseId.value = route.query.warehouseId || '';
+  title.value = route.query.warehouseName ? '扫码入库 - ' + route.query.warehouseName : '扫码入库';
+  getCarrierTypeList();
+
+  // 建立WebSocket连接
+  initWebSocket();
+});
+
+onUnmounted(() => {
+  // 关闭WebSocket连接
+  if (ws.value) {
+    ws.value.close();
+  }
+});
+
+// 初始化WebSocket连接
+const initWebSocket = () => {
+  const wsUrl = 'ws://127.0.0.1:10023';
+  ws.value = new WebSocket(wsUrl);
+
+  ws.value.onopen = () => {
+    console.log('WebSocket连接已建立');
+  };
+
+  ws.value.onmessage = event => {
+    handleWebSocketMessage(event);
+  };
+
+  ws.value.onerror = error => {
+    console.error('WebSocket错误:', error);
+  };
+
+  ws.value.onclose = () => {
+    console.log('WebSocket连接已关闭');
+  };
+};
+
+// 处理WebSocket消息
+const handleWebSocketMessage = event => {
+  try {
+    // 解析接收到的JSON数据
+    const data = JSON.parse(event.data);
+    console.log('接收到WebSocket数据:', data);
+
+    // 将数据赋值到对应字段
+    if (data.batchNumber) {
+      stockData.value.batchNo = data.batchNumber;
+    }
+    if (data.count) {
+      stockData.value.num = data.count.toString();
+    }
+
+    // 使用code调用getInfo方法
+    if (data.code) {
+      getInfo(data.code);
+    }
+  } catch (error) {
+    console.error('解析WebSocket消息失败:', error);
+    showFailToast('解析数据失败,请检查数据格式');
+  }
+};
+
+const columns = ref([]);
+const showCarrierTypePicker = ref(false);
+const carrierType = ref([]);
+const onConfirm = ({ selectedValues, selectedOptions }) => {
+  showCarrierTypePicker.value = false;
+  stockData.value.carrierType = selectedValues[0];
+  stockData.value.carrierTypeName = selectedOptions[0].text;
+};
+
+const goBack = () => {
+  router.back();
+};
+
+const goMenu = () => {
+  router.push('/app-menus');
+};
+// 处理中转区货位选择结果
+const onTransferPositionSelected = item => {
+  stockData.value.transferId = item.id;
+  stockData.value.transferNo = item.no;
+  stockData.value.transferName = item.name;
+};
+
+// 处理入库货位选择结果
+const onIdlePositionSelected = item => {
+  stockData.value.idleId = item.id;
+  stockData.value.idleNo = item.no;
+  stockData.value.idleName = item.name;
+};
+
+// 清除表单数据
+const clearFormData = () => {
+  stockData.value = {
+    id: '',
+    no: '',
+    name: '',
+    type: '',
+    num: '',
+    batchNo: '',
+    transferId: '',
+    transferName: '',
+    transferNo: '',
+    idleId: '',
+    idleName: '',
+    idleNo: '',
+    inventoryPackaged: false,
+    carrierType:'',
+    carrierTypeName:'',
+  };
+  carrierType.value = [];
+  transferPositionSelector.value.clearSelected();
+  idlePositionSelector.value.clearSelected();
+};
+
+// 提交入库
+const submit = () => {
+  console.log(stockData.value);
+  if (!stockData.value.no) {
+    showFailToast({ duration: 4000, message: '请等待扫码数据并确认入库信息后再提交' });
+    return;
+  }
+  if (!stockData.value.num) {
+    showFailToast('请输入入库数量');
+    return;
+  }
+  if (!stockData.value.transferId) {
+    showFailToast('请选择中转区货位');
+    return;
+  }
+  if (!stockData.value.idleId) {
+    showFailToast('请选择入库货位');
+    return;
+  }
+  if (!stockData.value.carrierType) {
+    showFailToast('请选择托盘类型');
+    return;
+  }
+  showConfirmDialog({
+    title: '确认要入库吗?',
+    message: '如果确认要入库,请点击【确认】按钮,否则点击【取消】按钮。',
+  })
+    .then(() => {
+      submitStockIn();
+    })
+    .catch(() => {
+      console.log('取消');
+    });
+};
+
+
+
+// 获取入库物料详情
+const getInfo = no => {
+  showFullscreenLoading();
+  const url = `/api/InventoryResource/queryStockInInventory?start=0&length=1&filter=${no}`;
+  ajaxApiGet(url).then(
+    success => {
+      const { errorCode, errorMessage, datas } = success;
+      if (errorCode === 0) {
+        if (datas && datas.length) {
+          stockData.value = { ...stockData.value, ...datas[0] };
+          showSuccessToast({ duration: 1000, message: '获取信息成功。' });
+        }
+      } else {
+        showFailToast({ duration: 1000, message: errorMessage });
+      }
+      hideFullscreenLoading();
+    },
+    error => {
+      hideFullscreenLoading();
+      processException(error);
+    },
+  );
+};
+
+// 提交API
+const submitStockIn = () => {
+  const url = '/api/stockInResource/scanGeneratorStockIn';
+  const params = JSON.parse(JSON.stringify(stockData.value));
+  delete params.workDate;
+  ajaxApiPost(url, params).then(
+    success => {
+      if (success.errorCode === 0) {
+        showSuccessToast('入库成功');
+        clearFormData();
+      } else {
+        showFailToast(success.errorMessage);
+      }
+    },
+    error => {
+      processException(error);
+    },
+  );
+};
+
+const getCarrierTypeList = () => {
+  const url = '/api/CarrierTypeResource/queryAllType';
+  ajaxApiGet(url).then(
+    success => {
+      const { errorCode, errorMessage, datas, total } = success;
+      if (errorCode === 0) {
+        if (datas && datas.length) {
+          columns.value = datas.map(item => ({ text: item.name, value: item.id }));
+        } else {
+          columns.value = [];
+        }
+      } else {
+        showFailToast(errorMessage);
+      }
+    },
+    error => {
+      processException(error);
+    },
+  );
+};
+</script>
+
+<style scoped>
+.content {
+  margin-top: 10px;
+}
+
+.scan-btn {
+  margin: 0 10px;
+}
+
+.custom-picker {
+  display: flex !important;
+  flex-direction: column !important;
+  height: 100% !important;
+}
+
+.picker-header {
+  display: flex !important;
+  justify-content: space-between !important;
+  align-items: center !important;
+  padding: 10px 16px !important;
+  border-bottom: 1px solid #ebedf0 !important;
+}
+
+.picker-title {
+  font-size: 16px !important;
+  font-weight: 500 !important;
+}
+
+.picker-content {
+  flex: 1 !important;
+  overflow-y: auto !important;
+}
+
+.picker-footer {
+  padding: 10px 16px !important;
+  border-top: 1px solid #ebedf0 !important;
+  display: flex !important;
+  flex-direction: column !important;
+  gap: 8px !important;
+}
+
+.loading-more {
+  text-align: center !important;
+  color: #969799 !important;
+  padding: 10px 0 !important;
+}
+</style>

+ 2 - 2
src/components/StockOut.vue

@@ -1,6 +1,6 @@
 <template>
   <van-nav-bar
-    :title="title" left-arrow left-text="返回" right-text="拍照" fixed placeholder @click-left="goBack()"
+    :title="title" left-arrow left-text="返回" right-text="扫描" fixed placeholder @click-left="goBack()"
     @click-right="goTakePhoto"
   />
   <van-search v-model="stockOutSearch" placeholder="请输入搜索关键词" @search="searchStockOut" />
@@ -329,7 +329,7 @@ const clearFormData = () => {
 
 // 拍照
 const goTakePhoto = () => {
-  router.push('/stock-out-photo?warehouseId=' + warehouseId.value + '&warehouseName=' + warehouseName.value);
+  router.push('/stock-out-scan?warehouseId=' + warehouseId.value + '&warehouseName=' + warehouseName.value);
 };
 onMounted(() => {
   loadStockOutList();

+ 359 - 0
src/components/StockOutScan.vue

@@ -0,0 +1,359 @@
+<template>
+  <van-nav-bar :title="title" left-arrow left-text="返回" fixed placeholder right-text="返回菜单" @click-left="goBack()" @click-right="goMenu" />
+  <div class="content">
+    <div class="scan-btn">
+      <van-button block plain icon="orders-o" type="primary">
+        等待扫码数据
+      </van-button>
+    </div>
+    <van-form :scroll-to-error="true">
+      <van-field v-model="stockData.inventoryNo" name="inventoryNo" label="物料编号:" :readonly="true" />
+
+      <van-field v-model="stockData.inventoryName" name="inventoryName" label="物料名称:" :readonly="true" />
+
+      <van-field v-model="stockData.inventoryType" name="inventoryType" label="规格型号:" :readonly="true" />
+
+      <van-field v-model="stockData.batchNo" name="batchNo" label="批号:" :readonly="true" type="textarea" rows="1" autosize />
+
+      <van-field name="outAll" label="是否全部出库">
+        <template #input>
+          <van-switch v-model="stockData.outAll" size="16px" />
+        </template>
+      </van-field>
+
+      <van-field
+        v-model="stockData.quantity" name="quantity" label="出库数量:" placeholder="点击输入数量"
+        :readonly="stockData.outAll"
+      />
+
+      <van-field
+        v-model="stockData.transferName" is-link readonly name="transfer" label="中转区货位:"
+        placeholder="点击选择中转区货位" @click="showTransferClick"
+      />
+      <van-field
+        v-model="stockData.positionName" is-link readonly name="warehouse" label="出库货位:" placeholder="点击选择出库货位"
+        @click="showUsingClick"
+      />
+      <div style="margin: 16px">
+        <van-button round block type="primary" @click="submit">
+          提交
+        </van-button>
+      </div>
+    </van-form>
+  </div>
+  <div>
+    <position-selector
+      ref="transferPositionSelector" v-model:show="isShowTransfer" position-type="transfer" :warehouse-id="warehouseId"
+      is-user="stockOut" :default-selected-id="stockData.transferId" @confirm="onTransferPositionSelected"
+    />
+    <position-selector
+      ref="usingPositionSelector" v-model:show="isShowUsing" position-type="using" is-user="stockOut"
+      :warehouse-id="warehouseId" :default-selected-id="stockData.positionId" @confirm="onUsingPositionSelected"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import PositionSelector from './PositionSelector.vue';
+import { processException } from '../common/Common.js';
+import { ajaxApiGet, ajaxApiPost } from '../common/utils.js';
+import { showSuccessToast, showFailToast, showConfirmDialog } from 'vant';
+import { showFullscreenLoading, hideFullscreenLoading } from '../common/loading.js';
+
+const route = useRoute();
+const router = useRouter();
+
+// WebSocket实例
+const ws = ref(null);
+
+// 状态
+const isShowTransfer = ref(false);
+const isShowUsing = ref(false);
+
+const transferPositionSelector = ref(null);
+const usingPositionSelector = ref(null);
+
+const stockData = ref({
+  currentStockId: '',
+  inventoryId: '',
+  inventoryNo: '',
+  inventoryName: '',
+  inventoryType: '',
+  quantity: '',
+  batchNo: '',
+  transferName: '',
+  transferId: '',
+  transferNo: '',
+  positionId: '',
+  positionNo: '',
+  positionName: '',
+  outAll: true,
+  warehouseId: '',
+});
+
+const warehouseId = ref('');
+const title  = ref('');
+
+onMounted(() => {
+  warehouseId.value = route.query.warehouseId || '';
+  title.value = route.query.warehouseName ? '扫描出库 - ' + route.query.warehouseName : '扫描出库';
+  stockData.value.warehouseId = warehouseId.value;
+  // 建立WebSocket连接
+  initWebSocket();
+});
+
+onUnmounted(() => {
+  // 关闭WebSocket连接
+  if (ws.value) {
+    ws.value.close();
+  }
+});
+
+// 初始化WebSocket连接
+const initWebSocket = () => {
+  const wsUrl = 'ws://127.0.0.1:10023';
+  ws.value = new WebSocket(wsUrl);
+
+  ws.value.onopen = () => {
+    console.log('WebSocket连接已建立');
+  };
+
+  ws.value.onmessage = event => {
+    handleWebSocketMessage(event);
+  };
+
+  ws.value.onerror = error => {
+    console.error('WebSocket错误:', error);
+  };
+
+  ws.value.onclose = () => {
+    console.log('WebSocket连接已关闭');
+  };
+};
+
+// 处理WebSocket消息
+const handleWebSocketMessage = event => {
+  try {
+    // 解析接收到的JSON数据
+    const data = JSON.parse(event.data);
+    console.log('接收到WebSocket数据:', data);
+
+    // 获取positionId(出库货位)
+    const positionId = stockData.value.positionId || '';
+
+    // 使用code调用getInfo方法
+    if (data.code) {
+      getInfo(data.code, data.batchNumber, positionId);
+    }
+  } catch (error) {
+    console.error('解析WebSocket消息失败:', error);
+    showFailToast('解析数据失败,请检查数据格式');
+  }
+};
+
+const goBack = () => {
+  router.back();
+};
+
+const goMenu = () => {
+  router.push('/app-menus');
+};
+
+const showTransferClick = () => {
+  isShowTransfer.value = true;
+};
+const showUsingClick = () => {
+  isShowUsing.value = true;
+};
+
+// 处理中转区货位选择结果
+const onTransferPositionSelected = item => {
+  stockData.value.transferId = item.id;
+  stockData.value.transferNo = item.no;
+  stockData.value.transferName = item.name;
+};
+
+// 处理出库货位选择结果
+const onUsingPositionSelected = item => {
+
+  if(!stockData.value.inventoryId){
+    console.log('inventory不存在,不调用接口');
+    stockData.value.positionId = item.id;
+    stockData.value.positionNo = item.no;
+    stockData.value.positionName = item.name;
+    return;
+  }
+  getInfo(stockData.value.inventoryNo, stockData.value.batchNo, item.id);
+};
+
+// 清除表单数据
+const clearFormData = () => {
+  stockData.value = {
+    currentStockId: '',
+    inventoryId: '',
+    inventoryNo: '',
+    inventoryName: '',
+    inventoryType: '',
+    quantity: '',
+    batchNo: '',
+    transferName: '',
+    transferId: '',
+    transferNo: '',
+    positionId: '',
+    positionNo: '',
+    positionName: '',
+    outAll: true,
+    warehouseId: warehouseId.value,
+  };
+  transferPositionSelector.value.clearSelected();
+  usingPositionSelector.value.clearSelected();
+};
+
+// 提交出库
+const submit = () => {
+  if (!stockData.value.inventoryNo) {
+    showFailToast({ duration: 1000, message: '请等待扫码数据并确认出库信息后再提交' });
+    return;
+  }
+  if (!stockData.value.quantity) {
+    showFailToast('请输入出库数量');
+    return;
+  }
+  if (!stockData.value.transferId) {
+    showFailToast('请选择中转区货位');
+    return;
+  }
+  if (!stockData.value.positionId) {
+    showFailToast('请选择出库货位');
+    return;
+  }
+  showConfirmDialog({
+    title: '确认要出库吗?',
+    message: '如果确认要出库,请点击【确认】按钮,否则点击【取消】按钮。',
+  })
+    .then(() => {
+      submitStockOut();
+    })
+    .catch(() => {
+      console.log('取消');
+    });
+};
+
+// 获取出库物料详情
+const getInfo = (no, batchNo, positionId = '') => {
+
+  showFullscreenLoading();
+  const url = `/api/StockOutResource/queryByInventoryNoAndBatchNo?no=${no}&batchNo=${batchNo}&positionId=${positionId}&warehouseId=${warehouseId.value}`;
+  ajaxApiGet(url).then(
+    success => {
+      const { errorCode, errorMessage, data } = success;
+      if (errorCode === 0) {
+        if (data) {
+          stockData.value = { ...stockData.value, ...data };
+          showSuccessToast('获取信息成功。');
+        }
+      } else {
+        showFailToast({ duration: 2000, message: errorMessage });
+      }
+      hideFullscreenLoading();
+    },
+    error => {
+      hideFullscreenLoading();
+      processException(error);
+    },
+  );
+};
+
+// 提交API
+const submitStockOut = () => {
+  const url = '/api/StockOutResource/scanGeneratorStockOut';
+  const params = JSON.parse(JSON.stringify(stockData.value));
+  delete params.workDate;
+  ajaxApiPost(url, params).then(
+    success => {
+      if (success.errorCode === 0) {
+        showSuccessToast('出库成功');
+        clearFormData();
+      } else {
+        showFailToast(success.errorMessage);
+      }
+    },
+    error => {
+      processException(error);
+    },
+  );
+};
+
+// 根据货位、物料、批号查询库存
+const getStockOutInfo = info => {
+  const positionId = info.id;
+  const inventoryId = stockData.value.inventoryId;
+  const url = `/api/StockOutResource/queryByInventoryAndPosition?positionId=${positionId}&inventoryId=${inventoryId}`;
+  ajaxApiGet(url).then(
+    success => {
+      const { errorCode, errorMessage, data } = success;
+      if (errorCode === 0) {
+        console.log(data, '根据货位、物料、批号查询库存');
+        if (data) {
+          stockData.value = { ...stockData.value, ...data };
+        }
+      } else {
+        showFailToast(errorMessage);
+      }
+    },
+    error => {
+      processException(error);
+    },
+  );
+};
+</script>
+
+<style scoped>
+.content {
+  margin-top: 10px;
+}
+
+.scan-btn {
+  margin: 0 10px;
+}
+
+.custom-picker {
+  display: flex !important;
+  flex-direction: column !important;
+  height: 100% !important;
+}
+
+.picker-header {
+  display: flex !important;
+  justify-content: space-between !important;
+  align-items: center !important;
+  padding: 10px 16px !important;
+  border-bottom: 1px solid #ebedf0 !important;
+}
+
+.picker-title {
+  font-size: 16px !important;
+  font-weight: 500 !important;
+}
+
+.picker-content {
+  flex: 1 !important;
+  overflow-y: auto !important;
+}
+
+.picker-footer {
+  padding: 10px 16px !important;
+  border-top: 1px solid #ebedf0 !important;
+  display: flex !important;
+  flex-direction: column !important;
+  gap: 8px !important;
+}
+
+.loading-more {
+  text-align: center !important;
+  color: #969799 !important;
+  padding: 10px 0 !important;
+}
+</style>

+ 2 - 2
src/components/StockTransfer.vue

@@ -1,6 +1,6 @@
 <template>
   <van-nav-bar
-    :title="title" left-arrow left-text="返回" right-text="拍照" fixed placeholder @click-left="goBack()"
+    :title="title" left-arrow left-text="返回" right-text="扫描" fixed placeholder @click-left="goBack()"
     @click-right="goTakePhoto"
   />
   <van-search v-model="stockTransferSearch" placeholder="请输入搜索关键词" @search="searchStockTransfer" />
@@ -306,7 +306,7 @@ const clearFormData = () => {
 
 // 拍照
 const goTakePhoto = () => {
-  router.push('/stock-transfer-photo?warehouseId=' + warehouseId.value + '&warehouseName=' + warehouseName.value);
+  router.push('/stock-transfer-scan?warehouseId=' + warehouseId.value + '&warehouseName=' + warehouseName.value);
 };
 onMounted(() => {
   warehouseId.value = route.query.warehouseId || '';

+ 348 - 0
src/components/StockTransferScan.vue

@@ -0,0 +1,348 @@
+<template>
+  <van-nav-bar :title="title" left-arrow left-text="返回" fixed placeholder right-text="返回菜单" @click-left="goBack()" @click-right="goMenu" />
+  <div class="content">
+    <div class="scan-btn">
+      <van-button block plain icon="orders-o" type="primary">
+        等待扫码数据
+      </van-button>
+    </div>
+    <van-form :scroll-to-error="true">
+      <van-field v-model="stockData.inventoryNo" name="inventoryNo" label="物料编号:" :readonly="true" />
+
+      <van-field v-model="stockData.inventoryName" name="inventoryName" label="物料名称:" :readonly="true" />
+
+      <van-field v-model="stockData.inventoryType" name="inventoryType" label="规格型号:" :readonly="true" />
+
+      <van-field v-model="stockData.quantity" name="inventoryUnit" label="库存:" :readonly="true" />
+
+      <van-field v-model="stockData.batchNo" name="batchNo" label="批号:" :readonly="true" type="textarea" rows="1" autosize />
+
+      <van-field
+        v-model="stockData.positionBeforeName" is-link readonly name="warehouse" label="调拨前货位:"
+        placeholder="点击选择调拨前货位" :default-selected-id="stockData.positionBeforeId" @click="showCurrentClick"
+      />
+      <van-field
+        v-model="stockData.positionAfterName" is-link readonly name="warehouse" label="调拨后货位:"
+        placeholder="点击选择调拨后货位" @click="showTransferClick"
+      />
+
+      <div style="margin: 16px">
+        <van-button round block type="primary" @click="submit">
+          提交
+        </van-button>
+      </div>
+    </van-form>
+  </div>
+  <div>
+    <position-selector
+      ref="currentPositionSelector" v-model:show="isShowCurrent" position-type="current" :warehouse-id="warehouseId"
+      is-user="transfer" :default-selected-id="stockData.positionBeforeId" @confirm="currentPositionSelected"
+    />
+    <position-selector
+      ref="transferPositionSelector" v-model:show="isShowTransfer" position-type="idle" :warehouse-id="warehouseId"
+      is-user="transfer" @confirm="onTransferPositionSelected"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import PositionSelector from './PositionSelector.vue';
+import { processException } from '../common/Common.js';
+import { ajaxApiGet, ajaxApiPost } from '../common/utils.js';
+import { showSuccessToast, showFailToast, showConfirmDialog } from 'vant';
+import { showFullscreenLoading, hideFullscreenLoading } from '../common/loading.js';
+
+
+const route = useRoute();
+const router = useRouter();
+
+// WebSocket实例
+const ws = ref(null);
+
+// 状态
+const isShowCurrent = ref(false);
+const isShowTransfer = ref(false);
+
+const currentPositionSelector = ref(null);
+const transferPositionSelector = ref(null);
+
+const stockData = ref({
+  currentStockId: '',
+  inventoryId: '',
+  inventoryNo: '',
+  inventoryName: '',
+  inventoryType: '',
+  quantity: '',
+  batchNo: '',
+  positionBeforeName: '',
+  positionBeforeId: '',
+  positionBeforeNo: '',
+  positionAfterId: '',
+  positionAfterNo: '',
+  positionAfterName: '',
+  warehouseId: '',
+});
+
+const warehouseId = ref(null);
+const title = ref('');
+
+onMounted(() => {
+  warehouseId.value = route.query.warehouseId || '';
+  title.value = route.query.warehouseName ? '扫描调拨 - ' + route.query.warehouseName : '扫描调拨';
+  stockData.value.warehouseId = warehouseId.value;
+  // 建立WebSocket连接
+  initWebSocket();
+});
+
+onUnmounted(() => {
+  // 关闭WebSocket连接
+  if (ws.value) {
+    ws.value.close();
+  }
+});
+
+// 初始化WebSocket连接
+const initWebSocket = () => {
+  const wsUrl = 'ws://127.0.0.1:10023';
+  ws.value = new WebSocket(wsUrl);
+
+  ws.value.onopen = () => {
+    console.log('WebSocket连接已建立');
+  };
+
+  ws.value.onmessage = event => {
+    handleWebSocketMessage(event);
+  };
+
+  ws.value.onerror = error => {
+    console.error('WebSocket错误:', error);
+  };
+
+  ws.value.onclose = () => {
+    console.log('WebSocket连接已关闭');
+  };
+};
+
+// 处理WebSocket消息
+const handleWebSocketMessage = event => {
+  try {
+    // 解析接收到的JSON数据
+    const data = JSON.parse(event.data);
+    console.log('接收到WebSocket数据:', data);
+
+    // 获取positionBeforeId(调拨前货位)
+    const positionBeforeId = stockData.value.positionBeforeId || '';
+
+    // 使用code调用getInfo方法
+    if (data.code) {
+      getInfo(data.code, data.batchNumber, positionBeforeId);
+    }
+  } catch (error) {
+    console.error('解析WebSocket消息失败:', error);
+    showFailToast('解析数据失败,请检查数据格式');
+  }
+};
+
+const goBack = () => {
+  router.back();
+};
+
+const goMenu = () => {
+  router.push('/app-menus');
+};
+
+const showCurrentClick = () => {
+  isShowCurrent.value = true;
+};
+const showTransferClick = () => {
+  isShowTransfer.value = true;
+};
+
+// 处理调拨前货位选择结果
+const currentPositionSelected = item => {
+
+  if(!stockData.value.inventoryId){
+    console.log('inventory不存在,不调用接口');
+    stockData.value.positionBeforeId = item.id;
+    stockData.value.positionBeforeNo = item.no;
+    stockData.value.positionBeforeName = item.name;
+    return;
+  }
+  getInfo(stockData.value.inventoryNo, stockData.value.batchNo, item.id);
+};
+
+// 处理调拨后货位选择结果
+const onTransferPositionSelected = item => {
+  stockData.value.positionAfterId = item.id;
+  stockData.value.positionAfterNo = item.no;
+  stockData.value.positionAfterName = item.name;
+};
+
+// 清除表单数据
+const clearFormData = () => {
+  stockData.value = {
+    currentStockId: '',
+    inventoryId: '',
+    inventoryNo: '',
+    inventoryName: '',
+    inventoryType: '',
+    quantity: '',
+    batchNo: '',
+    positionBeforeName: '',
+    positionBeforeId: '',
+    positionBeforeNo: '',
+    positionAfterId: '',
+    positionAfterNo: '',
+    positionAfterName: '',
+    warehouseId: warehouseId.value,
+  };
+  currentPositionSelector.value.clearSelected();
+  transferPositionSelector.value.clearSelected();
+};
+
+// 提交调拨
+const submit = () => {
+  if (!stockData.value.inventoryNo) {
+    showFailToast({ duration: 1000, message: '请等待扫码数据并确认调拨信息后再提交' });
+    return;
+  }
+  if (!stockData.value.positionBeforeId) {
+    showFailToast('请选择调拨前货位');
+    return;
+  }
+  if (!stockData.value.positionAfterId) {
+    showFailToast('请选择调拨后货位');
+    return;
+  }
+  showConfirmDialog({
+    title: '确认要调拨吗?',
+    message: '如果确认要调拨,请点击【确认】按钮,否则点击【取消】按钮。',
+  })
+    .then(() => {
+      submitTransfer();
+    })
+    .catch(() => {
+      console.log('取消');
+    });
+};
+
+// 获取调拨物料详情
+const getInfo = (no, batchNo, positionBeforeId = '') => {
+
+  showFullscreenLoading();
+  const url = `/api/StockOutResource/queryByInventoryNoAndBatchNo?no=${no}&batchNo=${batchNo}&positionId=${positionBeforeId}&warehouseId=${warehouseId.value}`;
+  ajaxApiGet(url).then(
+    success => {
+      const { errorCode, errorMessage, data } = success;
+      if (errorCode === 0) {
+        if (data) {
+          stockData.value = { ...stockData.value, ...data };
+          stockData.value.positionBeforeId = positionBeforeId || data.positionId;
+          stockData.value.positionBeforeNo = data.positionNo;
+          stockData.value.positionBeforeName = data.positionName;
+          showSuccessToast('获取信息成功。' );
+        }
+      } else {
+        showFailToast({ duration: 2000, message: errorMessage });
+      }
+      hideFullscreenLoading();
+    },
+    error => {
+      hideFullscreenLoading();
+      processException(error);
+    },
+  );
+};
+
+// 提交API
+const submitTransfer = () => {
+  const url = '/api/adjustPositionResource/scanGeneratorAdjustPosition';
+  const params = JSON.parse(JSON.stringify(stockData.value));
+  delete params.workDate;
+  ajaxApiPost(url, params).then(
+    success => {
+      if (success.errorCode === 0) {
+        showSuccessToast('调拨成功');
+        clearFormData();
+      } else {
+        showFailToast(success.errorMessage);
+      }
+    },
+    error => {
+      processException(error);
+    },
+  );
+};
+
+// 根据货位、物料、批号查询库存
+const getTransferInfo = info => {
+  const positionId = info.id;
+  const inventoryId = stockData.value.inventoryId;
+  const url = `/api/StockOutResource/queryByInventoryAndPosition?positionId=${positionId}&inventoryId=${inventoryId}`;
+  ajaxApiGet(url).then(
+    success => {
+      const { errorCode, errorMessage, data } = success;
+      if (errorCode === 0) {
+        if (data) {
+          stockData.value = { ...stockData.value, ...data };
+        }
+      } else {
+        showFailToast(errorMessage);
+      }
+    },
+    error => {
+      processException(error);
+    },
+  );
+};
+</script>
+
+<style scoped>
+.content {
+  margin-top: 10px;
+}
+
+.scan-btn {
+  margin: 0 10px;
+}
+
+.custom-picker {
+  display: flex !important;
+  flex-direction: column !important;
+  height: 100% !important;
+}
+
+.picker-header {
+  display: flex !important;
+  justify-content: space-between !important;
+  align-items: center !important;
+  padding: 10px 16px !important;
+  border-bottom: 1px solid #ebedf0 !important;
+}
+
+.picker-title {
+  font-size: 16px !important;
+  font-weight: 500 !important;
+}
+
+.picker-content {
+  flex: 1 !important;
+  overflow-y: auto !important;
+}
+
+.picker-footer {
+  padding: 10px 16px !important;
+  border-top: 1px solid #ebedf0 !important;
+  display: flex !important;
+  flex-direction: column !important;
+  gap: 8px !important;
+}
+
+.loading-more {
+  text-align: center !important;
+  color: #969799 !important;
+  padding: 10px 0 !important;
+}
+</style>

+ 13 - 0
src/router/router.js

@@ -30,6 +30,16 @@ const StockTransfer = () => import('../components/StockTransfer.vue')
 // 调拨拍照
 const StockTransferPhoto = () => import('../components/StockTransferPhoto.vue') 
 
+// 入库扫描
+const StockInScan = () => import('../components/StockInScan.vue')
+
+// 出库扫描
+const StockOutScan = () => import('../components/StockOutScan.vue')
+
+// 调拨扫描
+const StockTransferScan = () => import('../components/StockTransferScan.vue')
+
+
 
 export default [
   { path: '/', redirect: '/login', meta: { requiresAuth: false }, },
@@ -43,4 +53,7 @@ export default [
   { path: '/stock-transfer', component: StockTransfer, meta: { requiresAuth: true }, },
   { path: '/stock-transfer-photo', component: StockTransferPhoto, meta: { requiresAuth: true }, },
   { path: '/stock-out-back', component: StockOutBack, meta: { requiresAuth: true }, },
+  { path: '/stock-in-scan', component: StockInScan, meta: { requiresAuth: true }, },
+  { path: '/stock-out-scan', component: StockOutScan, meta: { requiresAuth: true }, },
+  { path: '/stock-transfer-scan', component: StockTransferScan, meta: { requiresAuth: true }, },
 ];