liuyanpeng 11 месяцев назад
Родитель
Сommit
e0b56d3acd

+ 9 - 2
src/components/PositionSelector.vue

@@ -47,7 +47,7 @@ const props = defineProps({
   positionType: {
   positionType: {
     type: String,
     type: String,
     default: 'transfer', // 'transfer' 或 'idle'
     default: 'transfer', // 'transfer' 或 'idle'
-    validator: value => ['transfer', 'idle'].includes(value),
+    validator: value => ['transfer', 'idle', 'current', 'using'].includes(value),
   },
   },
   isUser: {
   isUser: {
     type: String,
     type: String,
@@ -94,6 +94,10 @@ const title = computed(() =>{
       transfer: '选择中转区货位',
       transfer: '选择中转区货位',
       using: '选择出库货位',
       using: '选择出库货位',
     },
     },
+    transfer: {
+      current: '选择调拨前货位',
+      idle: '选择调拨后货位',
+    },
   };
   };
   return titleMap[props.isUser]?.[props.positionType] || '选择货位';
   return titleMap[props.isUser]?.[props.positionType] || '选择货位';
 });
 });
@@ -108,8 +112,11 @@ const apiUrl = computed(() => {
       transfer: '/api/positionResource/getTransferPosition',
       transfer: '/api/positionResource/getTransferPosition',
       using: '/api/positionResource/getOccupiedPosition',
       using: '/api/positionResource/getOccupiedPosition',
     },
     },
+    transfer: {
+      current: '/api/positionResource/getOccupiedPosition',
+      idle: '/api/positionResource/getIdlePosition',
+    },
   };
   };
-  console.log(props.isUser, props.positionType);
   return apiMap[props.isUser]?.[props.positionType] || '';
   return apiMap[props.isUser]?.[props.positionType] || '';
 });
 });
 
 

+ 0 - 2
src/components/StockIn.vue

@@ -118,7 +118,6 @@ const searchStockIn = value => {
 
 
 // 处理中转区货位选择结果
 // 处理中转区货位选择结果
 const onTransferPositionSelected = item => {
 const onTransferPositionSelected = item => {
-  console.log(item, '中转区货位选择结果');
   formData.value.transferId = item.id;
   formData.value.transferId = item.id;
   formData.value.transferNo = item.no;
   formData.value.transferNo = item.no;
   formData.value.transferName = item.name;
   formData.value.transferName = item.name;
@@ -126,7 +125,6 @@ const onTransferPositionSelected = item => {
 
 
 // 处理入库货位选择结果
 // 处理入库货位选择结果
 const onIdlePositionSelected = item => {
 const onIdlePositionSelected = item => {
-  console.log(item, '入库货位选择结果');
   formData.value.idleId = item.id;
   formData.value.idleId = item.id;
   formData.value.idleNo = item.no;
   formData.value.idleNo = item.no;
   formData.value.idleName = item.name;
   formData.value.idleName = item.name;

+ 7 - 3
src/components/StockInPhoto.vue

@@ -41,8 +41,8 @@
     </van-form>
     </van-form>
   </div>
   </div>
   <div>
   <div>
-    <position-selector v-model:show="isShowTransfer" position-type="transfer" @confirm="onTransferPositionSelected" />
-    <position-selector v-model:show="isShowIdle" position-type="idle" @confirm="onIdlePositionSelected" />
+    <position-selector ref="transferPositionSelector" v-model:show="isShowTransfer" position-type="transfer" @confirm="onTransferPositionSelected" />
+    <position-selector ref="idlePositionSelector" v-model:show="isShowIdle" position-type="idle" @confirm="onIdlePositionSelected" />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -65,6 +65,8 @@ const photoFile = ref(null);
 // 状态
 // 状态
 const isShowTransfer = ref(false);
 const isShowTransfer = ref(false);
 const isShowIdle = ref(false);
 const isShowIdle = ref(false);
+const transferPositionSelector = ref(null);
+const idlePositionSelector = ref(null);
 
 
 const stockData = ref({
 const stockData = ref({
   no: '',
   no: '',
@@ -130,6 +132,8 @@ const clearFormData = () => {
   };
   };
   imageUrl.value = '';
   imageUrl.value = '';
   photoFile.value = null;
   photoFile.value = null;
+  transferPositionSelector.value.clearSelected();
+  idlePositionSelector.value.clearSelected();
 };
 };
 
 
 // 提交入库
 // 提交入库
@@ -168,7 +172,7 @@ const uploadImage = async () => {
     showFailToast('请先拍摄图片后再进行识别');
     showFailToast('请先拍摄图片后再进行识别');
     return;
     return;
   }
   }
-  const url = 'http://192.168.1.109:10020/AppApi/OcrResource';
+  const url = 'http://127.0.0.1:10020/AppApi/OcrResource';
   const fileData = new FormData();
   const fileData = new FormData();
   fileData.append('image', photoFile.value);
   fileData.append('image', photoFile.value);
 
 

+ 8 - 7
src/components/StockOut.vue

@@ -5,9 +5,9 @@
   />
   />
   <van-search v-model="stockOutSearch" placeholder="请输入搜索关键词" @search="searchStockOut" />
   <van-search v-model="stockOutSearch" placeholder="请输入搜索关键词" @search="searchStockOut" />
   <div class="content">
   <div class="content">
-    <div class="van-list-stock-in">
+    <div>
       <van-list
       <van-list
-        v-model:loading="loading" class="list-stock-in" :finished="finished" finished-text=""
+        v-model:loading="loading" :finished="finished" finished-text=""
         :immediate-check="false" @load="loadStockOutList"
         :immediate-check="false" @load="loadStockOutList"
       >
       >
         <div v-for="(item, index) in stockOutList" :key="item.id" class="list-container">
         <div v-for="(item, index) in stockOutList" :key="item.id" class="list-container">
@@ -32,7 +32,7 @@
       <van-field v-model="formData.inventoryNo" name="inventoryNo" label="物料编号:" readonly />
       <van-field v-model="formData.inventoryNo" name="inventoryNo" label="物料编号:" readonly />
       <van-field v-model="formData.inventoryName" name="inventoryName" label="物料名称:" readonly />
       <van-field v-model="formData.inventoryName" name="inventoryName" label="物料名称:" readonly />
       <van-field v-model="formData.inventoryType" name="inventoryType" label="规格型号:" readonly />
       <van-field v-model="formData.inventoryType" name="inventoryType" label="规格型号:" readonly />
-      <van-field v-model="formData.batchNo" name="batchNo" label="批号:" placeholder="点击输入批号" />
+      <van-field v-model="formData.batchNo" name="batchNo" label="批号:" placeholder="点击输入批号" readonly />
       <van-field name="outAll" label="是否全部出库">
       <van-field name="outAll" label="是否全部出库">
         <template #input>
         <template #input>
           <van-switch v-model="formData.outAll" size="20px" />
           <van-switch v-model="formData.outAll" size="20px" />
@@ -97,6 +97,7 @@ const total = ref(0);
 const pageSize = ref(10);
 const pageSize = ref(10);
 
 
 const formData = ref({
 const formData = ref({
+  currentStockId: '',
   inventoryId: '',
   inventoryId: '',
   inventoryNo: '',
   inventoryNo: '',
   inventoryName: '',
   inventoryName: '',
@@ -130,7 +131,6 @@ const searchStockOut = value => {
 
 
 // 处理中转区货位选择结果
 // 处理中转区货位选择结果
 const onTransferPositionSelected = item => {
 const onTransferPositionSelected = item => {
-  console.log(item, '中转区货位选择结果');
   formData.value.transferId = item.id;
   formData.value.transferId = item.id;
   formData.value.transferNo = item.no;
   formData.value.transferNo = item.no;
   formData.value.transferName = item.name;
   formData.value.transferName = item.name;
@@ -138,13 +138,13 @@ const onTransferPositionSelected = item => {
 
 
 // 处理出库货位选择结果
 // 处理出库货位选择结果
 const onUsingPositionSelected = item => {
 const onUsingPositionSelected = item => {
-  console.log(item, '出库货位选择结果');
   getStockOutInfo(item);
   getStockOutInfo(item);
 };
 };
 
 
 // 出库
 // 出库
 const stockOut = item => {
 const stockOut = item => {
   console.log(item, '出库');
   console.log(item, '出库');
+  formData.value.currentStockId = item.currentStockId;
   formData.value.inventoryId = item.inventoryId;
   formData.value.inventoryId = item.inventoryId;
   formData.value.inventoryNo = item.inventoryNo;
   formData.value.inventoryNo = item.inventoryNo;
   formData.value.inventoryName = item.inventoryName;
   formData.value.inventoryName = item.inventoryName;
@@ -217,7 +217,7 @@ const loadStockOutList = async () => {
 const getList = (page, pageSize) => {
 const getList = (page, pageSize) => {
   const start = (page - 1) * pageSize;
   const start = (page - 1) * pageSize;
   const length = pageSize;
   const length = pageSize;
-  const filter = '';
+  const filter = stockOutSearch.value;
   const url = `/api/StockOutResource/queryCurrentStockStockOut?start=${start}&length=${length}&filter=${filter}`;
   const url = `/api/StockOutResource/queryCurrentStockStockOut?start=${start}&length=${length}&filter=${filter}`;
   return new Promise((resolve, reject) => {
   return new Promise((resolve, reject) => {
     ajaxApiGet(url).then(
     ajaxApiGet(url).then(
@@ -278,7 +278,7 @@ const getStockOutInfo = info => {
           formData.value = { ...formData.value, ...data };
           formData.value = { ...formData.value, ...data };
         }
         }
       } else {
       } else {
-        showFailToast(errorMessage);
+        showFailToast({duration:4000, message:errorMessage});
       }
       }
     },
     },
     error => {
     error => {
@@ -295,6 +295,7 @@ const cancelStockOut = () => {
 // 清除表单数据
 // 清除表单数据
 const clearFormData = () => {
 const clearFormData = () => {
   formData.value = {
   formData.value = {
+    currentStockId: '',
     inventoryId: '',
     inventoryId: '',
     inventoryNo: '',
     inventoryNo: '',
     inventoryName: '',
     inventoryName: '',

+ 2 - 2
src/components/StockOutBack.vue

@@ -4,9 +4,9 @@
   />
   />
   <van-search v-model="stockBackSearch" placeholder="请输入搜索关键词" @search="searchStockBack" />
   <van-search v-model="stockBackSearch" placeholder="请输入搜索关键词" @search="searchStockBack" />
   <div class="content">
   <div class="content">
-    <div class="van-list-stock-in">
+    <div>
       <van-list
       <van-list
-        v-model:loading="loading" class="list-stock-in" :finished="finished" finished-text=""
+        v-model:loading="loading" :finished="finished" finished-text=""
         :immediate-check="false" @load="loadStockOutBackList"
         :immediate-check="false" @load="loadStockOutBackList"
       >
       >
         <div v-for="(item, index) in stockBackList" :key="item.id" class="list-container">
         <div v-for="(item, index) in stockBackList" :key="item.id" class="list-container">

+ 10 - 4
src/components/StockOutPhoto.vue

@@ -21,7 +21,7 @@
 
 
       <van-field v-model="stockData.inventoryType" name="inventoryType" label="规格型号:" :readonly="true" />
       <van-field v-model="stockData.inventoryType" name="inventoryType" label="规格型号:" :readonly="true" />
 
 
-      <van-field v-model="stockData.batchNo" name="batchNo" label="批号:" placeholder="点击输入批号" />
+      <van-field v-model="stockData.batchNo" name="batchNo" label="批号:" :readonly="true" />
 
 
       <van-field name="outAll" label="是否全部出库">
       <van-field name="outAll" label="是否全部出库">
         <template #input>
         <template #input>
@@ -81,7 +81,11 @@ const photoFile = ref(null);
 const isShowTransfer = ref(false);
 const isShowTransfer = ref(false);
 const isShowUsing = ref(false);
 const isShowUsing = ref(false);
 
 
+const transferPositionSelector = ref(null);
+const usingPositionSelector = ref(null);
+
 const stockData = ref({
 const stockData = ref({
+  currentStockId: '',
   inventoryId: '',
   inventoryId: '',
   inventoryNo: '',
   inventoryNo: '',
   inventoryName: '',
   inventoryName: '',
@@ -124,13 +128,13 @@ const onTransferPositionSelected = item => {
 
 
 // 处理出库货位选择结果
 // 处理出库货位选择结果
 const onUsingPositionSelected = item => {
 const onUsingPositionSelected = item => {
-  console.log(item, '出库货位选择结果');
   getStockOutInfo(item);
   getStockOutInfo(item);
 };
 };
 
 
 // 清除表单数据
 // 清除表单数据
 const clearFormData = () => {
 const clearFormData = () => {
   stockData.value = {
   stockData.value = {
+    currentStockId: '',
     inventoryId: '',
     inventoryId: '',
     inventoryNo: '',
     inventoryNo: '',
     inventoryName: '',
     inventoryName: '',
@@ -147,6 +151,8 @@ const clearFormData = () => {
   };
   };
   imageUrl.value = '';
   imageUrl.value = '';
   photoFile.value = null;
   photoFile.value = null;
+  transferPositionSelector.value.clearSelected();
+  usingPositionSelector.value.clearSelected();
 };
 };
 
 
 // 提交出库
 // 提交出库
@@ -185,7 +191,7 @@ const uploadImage = async () => {
     showFailToast('请先拍摄图片后再进行识别');
     showFailToast('请先拍摄图片后再进行识别');
     return;
     return;
   }
   }
-  const url = 'http://192.168.1.109:10020/AppApi/OcrResource';
+  const url = 'http://127.0.0.1:10020/AppApi/OcrResource';
   const fileData = new FormData();
   const fileData = new FormData();
   fileData.append('image', photoFile.value);
   fileData.append('image', photoFile.value);
 
 
@@ -228,7 +234,7 @@ const getInfo = (no, batchNo) => {
       if (errorCode === 0) {
       if (errorCode === 0) {
         if (data) {
         if (data) {
           stockData.value = { ...stockData.value, ...data };
           stockData.value = { ...stockData.value, ...data };
-          showSuccessToast({ duration: 5000, message: '获取信息成功,请先确认生产数量是否准确,若不准确请先修改数量。' });
+          showSuccessToast('获取信息成功。');
         }
         }
       } else {
       } else {
         showFailToast({ duration: 5000, message: errorMessage });
         showFailToast({ duration: 5000, message: errorMessage });

+ 338 - 0
src/components/StockTransfer.vue

@@ -0,0 +1,338 @@
+<template>
+  <van-nav-bar
+    title="调拨" left-arrow left-text="返回" right-text="拍照" fixed placeholder @click-left="goBack()"
+    @click-right="goTakePhoto"
+  />
+  <van-search v-model="stockTransferSearch" placeholder="请输入搜索关键词" @search="searchStockTransfer" />
+  <div class="content">
+    <div>
+      <van-list
+        v-model:loading="loading" :finished="finished" finished-text="" :immediate-check="false"
+        @load="loadTransferList"
+      >
+        <div v-for="(item, index) in transferList" :key="item.id" class="list-container">
+          <van-form :scroll-to-error="true">
+            <div class="in-header">
+              <strong>{{ index + 1 }}. {{ item.inventoryName }}</strong>
+              <van-button type="primary" plain @click="transfer(item)">调拨</van-button>
+            </div>
+            <van-field v-model="item.inventoryNo" name="inventoryNo" label="物料编号:" readonly />
+            <van-field v-model="item.inventoryName" name="inventoryName" label="物料名称:" readonly />
+            <van-field v-model="item.inventoryType" name="inventoryType" label="规格型号:" readonly />
+            <van-field v-model="item.quantity" name="quantity" label="库存:" readonly />
+            <van-field v-model="item.positionName" name="positionName" label="货位:" readonly />
+          </van-form>
+        </div>
+      </van-list>
+    </div>
+    <van-empty v-if="transferList.length === 0" description="暂无调拨库存" />
+  </div>
+  <van-dialog v-model:show="isShowStockTransfer" title="填写调拨信息" :show-confirm-button="false">
+    <van-form :scroll-to-error="true">
+      <van-field v-model="formData.inventoryNo" name="inventoryNo" label="物料编号:" readonly />
+      <van-field v-model="formData.inventoryName" name="inventoryName" label="物料名称:" readonly />
+      <van-field v-model="formData.inventoryType" name="inventoryType" label="规格型号:" readonly />
+      <van-field v-model="formData.quantity" name="quantity" label="库存:" readonly />
+      <van-field v-model="formData.batchNo" name="batchNo" label="批号:" placeholder="点击输入批号" readonly />
+      <van-field
+        v-model="formData.positionBeforeName" is-link readonly name="warehouse" label="调拨前货位:"
+        placeholder="点击选择调拨前货位" :default-selected-id="formData.positionBeforeId" @click="isShowCurrent = true"
+      />
+      <van-field
+        v-model="formData.positionAfterName" is-link readonly name="warehouse" label="调拨后货位:"
+        placeholder="点击选择调拨后货位" @click="isShowTransfer = true"
+      />
+    </van-form>
+
+    <template #footer>
+      <div class="footer-btn">
+        <van-button style="width: 40%;" plain type="primary" @click="cancelTransfer">
+          取消
+        </van-button>
+        <van-button style="width: 40%;" type="primary" @click="transferConfirm">
+          提交
+        </van-button>
+      </div>
+    </template>
+  </van-dialog>
+  <position-selector
+    ref="currentPositionSelector" v-model:show="isShowCurrent" position-type="current"
+    is-user="transfer" :default-selected-id="formData.positionBeforeId" @confirm="currentPositionSelected"
+  />
+  <position-selector
+    ref="transferPositionSelector" v-model:show="isShowTransfer" position-type="idle"
+    is-user="transfer" @confirm="onTransferPositionSelected"
+  />
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import { useRouter } from 'vue-router';
+import PositionSelector from './PositionSelector.vue';
+import { processException } from '../common/Common.js';
+import { ajaxApiGet, ajaxApiPost } from '../common/utils.js';
+import { showFailToast, showSuccessToast, showConfirmDialog } from 'vant';
+
+const router = useRouter();
+
+const stockTransferSearch = ref('');
+const currentPositionSelector = ref(null);
+const transferPositionSelector = ref(null);
+
+const transferList = ref([]);
+const loading = ref(false);
+const finished = ref(false);
+
+const page = ref(1);
+const total = ref(0);
+const pageSize = ref(10);
+
+const formData = ref({
+  currentStockId:'',
+  inventoryId: '',
+  inventoryNo: '',
+  inventoryName: '',
+  inventoryType: '',
+  quantity: '',
+  batchNo: '',
+  positionBeforeName: '',
+  positionBeforeId: '',
+  positionBeforeNo: '',
+  positionAfterId: '',
+  positionAfterNo: '',
+  positionAfterName: '',
+});
+
+const isShowStockTransfer = ref(false);
+const isShowCurrent = ref(false);
+const isShowTransfer = ref(false);
+
+const goBack = () => {
+  router.push('/app-menus');
+};
+
+// 搜索调拨物料
+const searchStockTransfer = value => {
+  page.value = 1;
+  total.value = 0;
+  transferList.value = [];
+  loadTransferList();
+};
+
+// 调拨前货位选择结果
+const currentPositionSelected = item => {
+  getTransferInfo(item);
+};
+// 调拨后货位选择结果
+const onTransferPositionSelected = item => {
+  formData.value.positionAfterId = item.id;
+  formData.value.positionAfterNo = item.no;
+  formData.value.positionAfterName = item.name;
+};
+
+// 调拨
+const transfer = item => {
+  console.log(item, '调拨');
+  formData.value.currentStockId = item.currentStockId;
+  formData.value.inventoryId = item.inventoryId;
+  formData.value.inventoryNo = item.inventoryNo;
+  formData.value.inventoryName = item.inventoryName;
+  formData.value.inventoryType = item.inventoryType;
+  formData.value.quantity = item.quantity;
+  formData.value.batchNo = item.batchNo;
+  formData.value.positionBeforeId = item.positionId;
+  formData.value.positionBeforeName = item.positionName;
+  formData.value.positionBeforeNo = item.positionNo;
+  isShowStockTransfer.value = true;
+};
+
+// 调拨确认
+const transferConfirm = () => {
+  console.log(formData.value, '调拨确认');
+
+  if (!formData.value.positionBeforeId) {
+    showFailToast('请选择调拨前货位');
+    return;
+  }
+  if (!formData.value.positionAfterId) {
+    showFailToast('请选择调拨后货位');
+    return;
+  }
+  showConfirmDialog({
+    title: '确认要调拨吗?',
+    message: '如果确认要调拨,请点击【确认】按钮,否则点击【取消】按钮。',
+  })
+    .then(() => {
+      submitTransfer();
+    })
+    .catch(() => {
+      console.log('取消');
+    });
+};
+
+// 加载调拨物料列表
+const loadTransferList = async () => {
+  try {
+    const res = await getList(page.value, pageSize.value);
+
+    // 搜索时替换数据,上拉加载时追加数据
+    transferList.value =
+      page.value === 1
+        ? res.data
+        : [...transferList.value, ...res.data];
+
+    total.value = res.total;
+
+    // 检查是否已加载全部数据
+    if (transferList.value.length >= total.value) {
+      finished.value = true;
+    } else {
+      page.value++; // 只有在成功加载后才增加页码
+    }
+  } catch (error) {
+    finished.value = true;
+    if (error.responseText === '未查询出对应库存信息。') return;
+    processException(error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 获取调拨物料列表API
+const getList = (page, pageSize) => {
+  const start = (page - 1) * pageSize;
+  const length = pageSize;
+  const filter = stockTransferSearch.value;
+  const url = `/api/StockOutResource/queryCurrentStockStockOut?start=${start}&length=${length}&filter=${filter}`;
+  return new Promise((resolve, reject) => {
+    ajaxApiGet(url).then(
+      success => {
+        const { errorCode, errorMessage, datas, total } = success;
+        if (errorCode === 0) {
+          if (datas && datas.length) {
+            resolve({ data: datas, total: total });
+          } else {
+            resolve({ data: [], total: 0 });
+          }
+        } else {
+          const error = { status: 200, responseText: errorMessage };
+          reject(error);
+        }
+      },
+      error => {
+        reject(error);
+      },
+    );
+  });
+};
+
+// 提交API
+const submitTransfer = () => {
+  const url = '/api/adjustPositionResource/scanGeneratorAdjustPosition';
+  const params = {
+    ...formData.value,
+  };
+  ajaxApiPost(url, params).then(
+    success => {
+      if (success.errorCode === 0) {
+        const filter = stockTransferSearch.value;
+        clearFormData();
+        searchStockTransfer(filter);
+        showSuccessToast('调拨成功');
+      } else {
+        showFailToast(success.errorMessage);
+      }
+    },
+    error => {
+      processException(error);
+    },
+  );
+};
+
+// 根据货位、物料、批号查询库存
+const getTransferInfo = info => {
+  const positionId = info.id;
+  const inventoryId = formData.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) {
+          formData.value = { ...formData.value, ...data };
+        }
+      } else {
+        showFailToast({ duration: 4000, message: errorMessage });
+      }
+    },
+    error => {
+      processException(error);
+    },
+  );
+};
+
+// 取消调拨
+const cancelTransfer = () => {
+  clearFormData();
+};
+
+// 清除表单数据
+const clearFormData = () => {
+  formData.value = {
+    currentStockId: '',
+    inventoryId: '',
+    inventoryNo: '',
+    inventoryName: '',
+    inventoryType: '',
+    quantity: '',
+    batchNo: '',
+    positionBeforeName: '',
+    positionBeforeId: '',
+    positionBeforeNo: '',
+    positionAfterId: '',
+    positionAfterNo: '',
+    positionAfterName: '',
+  };
+  currentPositionSelector.value.clearSelected();
+  transferPositionSelector.value.clearSelected();
+  isShowStockTransfer.value = false;
+};
+
+// 拍照
+const goTakePhoto = () => {
+  router.push('/stock-transfer-photo');
+};
+onMounted(() => {
+  loadTransferList();
+});
+
+</script>
+
+<style scoped>
+.van-search {
+  padding-bottom: 4px !important;
+}
+
+.list-container {
+  padding: 3px 10px;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  margin: 6px 12px;
+}
+
+.in-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 1px solid #ccc;
+  padding-bottom: 3px;
+}
+
+.footer-btn {
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  margin-bottom: 10px;
+}
+</style>

+ 330 - 0
src/components/StockTransferPhoto.vue

@@ -0,0 +1,330 @@
+<template>
+  <van-nav-bar title="拍照调拨" left-arrow left-text="返回" fixed placeholder @click-left="goBack()" />
+  <van-image v-if="imageUrl" width="100%" height="100%" :src="imageUrl" />
+  <div class="content">
+    <div class="scan-btn">
+      <input
+        ref="fileInput" type="file" accept="image/*" capture="camera" style="display: none"
+        @change="onFileChange"
+      />
+      <van-button block plain icon="scan" type="primary" @click="takePhoto">
+        拍摄
+      </van-button>
+      <van-button style="margin-top: 10px" block plain icon="orders-o" type="primary" @click="uploadImage">
+        识别
+      </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" />
+
+      <van-field
+        v-model="stockData.positionBeforeName" is-link readonly name="warehouse" label="调拨前货位:"
+        placeholder="点击选择调拨前货位" :default-selected-id="stockData.positionBeforeId" @click="isShowCurrent = true"
+      />
+      <van-field
+        v-model="stockData.positionAfterName" is-link readonly name="warehouse" label="调拨后货位:"
+        placeholder="点击选择调拨后货位" @click="isShowTransfer = true"
+      />
+
+      <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"
+      is-user="transfer" :default-selected-id="stockData.positionBeforeId" @confirm="currentPositionSelected"
+    />
+    <position-selector
+      ref="transferPositionSelector" v-model:show="isShowTransfer" position-type="idle"
+      is-user="transfer" @confirm="onTransferPositionSelected"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+import PositionSelector from './PositionSelector.vue';
+import { processException } from '../common/Common.js';
+import { ajaxApiGet, ajaxApiPost } from '../common/utils';
+import { showSuccessToast, showFailToast, showConfirmDialog } from 'vant';
+import { showFullscreenLoading, hideFullscreenLoading } from '../common/loading';
+
+
+const router = useRouter();
+
+const imageUrl = ref('');
+const fileInput = ref(null);
+const photoFile = 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: '',
+});
+
+const goBack = () => {
+  router.back();
+};
+
+const takePhoto = () => {
+  fileInput.value.click();
+};
+
+// 处理文件选择/拍照结果
+const onFileChange = event => {
+  const file = event.target.files[0];
+  if (!file) return;
+
+  imageUrl.value = URL.createObjectURL(file);
+
+  photoFile.value = file;
+
+};
+
+// 处理调拨前货位选择结果
+const currentPositionSelected = item => {
+  getTransferInfo(item);
+};
+
+// 处理调拨后货位选择结果
+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: '',
+  };
+  imageUrl.value = '';
+  photoFile.value = null;
+  currentPositionSelector.value.clearSelected();
+  transferPositionSelector.value.clearSelected();
+};
+
+// 提交调拨
+const submit = () => {
+  if (!stockData.value.inventoryNo) {
+    showFailToast({ duration: 4000, message: '请先拍摄并识别图片确认调拨信息后再提交' });
+    return;
+  }
+  if (!stockData.value.positionBeforeId) {
+    showFailToast('请选择调拨前货位');
+    return;
+  }
+  if (!stockData.value.positionAfterId) {
+    showFailToast('请选择调拨后货位');
+    return;
+  }
+  showConfirmDialog({
+    title: '确认要调拨吗?',
+    message: '如果确认要调拨,请点击【确认】按钮,否则点击【取消】按钮。',
+  })
+    .then(() => {
+      submitTransfer();
+    })
+    .catch(() => {
+      console.log('取消');
+    });
+};
+
+// 上传图片获取调拨信息
+const uploadImage = async () => {
+  if (!photoFile.value) {
+    showFailToast('请先拍摄图片后再进行识别');
+    return;
+  }
+  const url = 'http://127.0.0.1:10020/AppApi/OcrResource';
+  const fileData = new FormData();
+  fileData.append('image', photoFile.value);
+
+  showFullscreenLoading();
+  try {
+    const response = await fetch(url, {
+      method: 'POST',
+      body: fileData,
+    });
+
+    if (response.ok) {
+      const result = await response.json();
+
+      if (result.errorCode === 0) {
+        if (result.recognizeResult) {
+          // stockData.value = { ...result.recognizeResult };
+          getInfo(result.recognizeResult.no, result.recognizeResult.batchNo);
+        }
+      } else {
+        showFailToast({ duration: 4000, message: result.errorMessage });
+      }
+      hideFullscreenLoading();
+    } else {
+      hideFullscreenLoading();
+      showFailToast(`识别失败 (${response.status}): ${response.statusText}`);
+    }
+  } catch (error) {
+    hideFullscreenLoading();
+    showFailToast('识别图片失败: 请调整角度后重新拍摄');
+  }
+};
+
+// 获取调拨物料详情
+const getInfo = (no, batchNo) => {
+  showFullscreenLoading();
+  const url = `/api/StockOutResource/queryByInventoryNoAndBatchNo?no=${no}&batchNo=${batchNo}`;
+  ajaxApiGet(url).then(
+    success => {
+      const { errorCode, errorMessage, data } = success;
+      if (errorCode === 0) {
+        if (data) {
+          stockData.value = { ...stockData.value, ...data };
+          stockData.value.positionBeforeId = data.positionId;
+          stockData.value.positionBeforeNo = data.positionNo;
+          stockData.value.positionBeforeName = data.positionName;
+          showSuccessToast('获取信息成功。' );
+        }
+      } else {
+        showFailToast({ duration: 5000, 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>

+ 0 - 11
src/components/StockTtansfer.vue

@@ -1,11 +0,0 @@
-<template>
-  <div>
-    <h1>调拨管理</h1>
-  </div>
-</template>
-
-<script setup>
-
-</script>
-
-<style scoped></style>

+ 5 - 1
src/router/router.js

@@ -22,7 +22,10 @@ const StockOutPhoto = () => import('../components/StockOutPhoto.vue')
 const StockOutBack = () => import('../components/StockOutBack.vue')
 const StockOutBack = () => import('../components/StockOutBack.vue')
 
 
 // 调拨
 // 调拨
-const StockTransfer = () => import('../components/StockTtansfer.vue')
+const StockTransfer = () => import('../components/StockTransfer.vue')
+
+// 调拨拍照
+const StockTransferPhoto = () => import('../components/StockTransferPhoto.vue') 
 
 
 
 
 export default [
 export default [
@@ -34,5 +37,6 @@ export default [
   { path: '/stock-out', component: StockOut, meta: { requiresAuth: true }, },
   { path: '/stock-out', component: StockOut, meta: { requiresAuth: true }, },
   { path: '/stock-out-photo', component: StockOutPhoto, meta: { requiresAuth: true }, },
   { path: '/stock-out-photo', component: StockOutPhoto, meta: { requiresAuth: true }, },
   { path: '/stock-transfer', component: StockTransfer, meta: { requiresAuth: true }, },
   { 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-out-back', component: StockOutBack, meta: { requiresAuth: true }, },
 ];
 ];