StockOutPhoto.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. <template>
  2. <van-nav-bar title="拍照出库" left-arrow left-text="返回" fixed placeholder @click-left="goBack()" />
  3. <van-image v-if="imageUrl" width="100%" height="100%" :src="imageUrl" />
  4. <div class="content">
  5. <div class="scan-btn">
  6. <input
  7. ref="fileInput" type="file" accept="image/*" capture="camera" style="display: none"
  8. @change="onFileChange"
  9. />
  10. <van-button block plain icon="scan" type="primary" @click="takePhoto">
  11. 拍摄
  12. </van-button>
  13. <van-button style="margin-top: 10px" block plain icon="orders-o" type="primary" @click="uploadImage">
  14. 识别
  15. </van-button>
  16. </div>
  17. <van-form :scroll-to-error="true">
  18. <van-field v-model="stockData.inventoryNo" name="inventoryNo" label="物料编号:" :readonly="true" />
  19. <van-field v-model="stockData.inventoryName" name="inventoryName" label="物料名称:" :readonly="true" />
  20. <van-field v-model="stockData.inventoryType" name="inventoryType" label="规格型号:" :readonly="true" />
  21. <van-field v-model="stockData.batchNo" name="batchNo" label="批号:" :readonly="true" />
  22. <van-field name="outAll" label="是否全部出库">
  23. <template #input>
  24. <van-switch v-model="stockData.outAll" size="16px" />
  25. </template>
  26. </van-field>
  27. <van-field
  28. v-model="stockData.quantity" name="quantity" label="出库数量:" placeholder="点击输入数量"
  29. :readonly="stockData.outAll"
  30. />
  31. <van-field
  32. v-model="stockData.transferName" is-link readonly name="transfer" label="中转区货位:"
  33. placeholder="点击选择中转区货位" @click="isShowTransfer = true"
  34. />
  35. <van-field
  36. v-model="stockData.positionName" is-link readonly name="warehouse" label="出库货位:" placeholder="点击选择出库货位"
  37. @click="isShowUsing = true"
  38. />
  39. <div style="margin: 16px">
  40. <van-button round block type="primary" @click="submit">
  41. 提交
  42. </van-button>
  43. </div>
  44. </van-form>
  45. </div>
  46. <div>
  47. <position-selector
  48. ref="transferPositionSelector" v-model:show="isShowTransfer" position-type="transfer"
  49. is-user="stockOut" :default-selected-id="stockData.transferId" @confirm="onTransferPositionSelected"
  50. />
  51. <position-selector
  52. ref="usingPositionSelector" v-model:show="isShowUsing" position-type="using" is-user="stockOut"
  53. :default-selected-id="stockData.positionId" @confirm="onUsingPositionSelected"
  54. />
  55. </div>
  56. </template>
  57. <script setup>
  58. import { ref } from 'vue';
  59. import { useRouter } from 'vue-router';
  60. import PositionSelector from './PositionSelector.vue';
  61. import { processException } from '../common/Common.js';
  62. import { ajaxApiGet, ajaxApiPost } from '../common/utils';
  63. import { showSuccessToast, showFailToast, showConfirmDialog } from 'vant';
  64. import { showFullscreenLoading, hideFullscreenLoading } from '../common/loading';
  65. const router = useRouter();
  66. const imageUrl = ref('');
  67. const fileInput = ref(null);
  68. const photoFile = ref(null);
  69. // 状态
  70. const isShowTransfer = ref(false);
  71. const isShowUsing = ref(false);
  72. const transferPositionSelector = ref(null);
  73. const usingPositionSelector = ref(null);
  74. const stockData = ref({
  75. currentStockId: '',
  76. inventoryId: '',
  77. inventoryNo: '',
  78. inventoryName: '',
  79. inventoryType: '',
  80. quantity: '',
  81. batchNo: '',
  82. transferName: '',
  83. transferId: '',
  84. transferNo: '',
  85. positionId: '',
  86. positionNo: '',
  87. positionName: '',
  88. outAll: true,
  89. });
  90. const goBack = () => {
  91. router.back();
  92. };
  93. const takePhoto = () => {
  94. fileInput.value.click();
  95. };
  96. // 处理文件选择/拍照结果
  97. const onFileChange = event => {
  98. const file = event.target.files[0];
  99. if (!file) return;
  100. imageUrl.value = URL.createObjectURL(file);
  101. photoFile.value = file;
  102. };
  103. // 处理中转区货位选择结果
  104. const onTransferPositionSelected = item => {
  105. stockData.value.transferId = item.id;
  106. stockData.value.transferNo = item.no;
  107. stockData.value.transferName = item.name;
  108. };
  109. // 处理出库货位选择结果
  110. const onUsingPositionSelected = item => {
  111. getStockOutInfo(item);
  112. };
  113. // 清除表单数据
  114. const clearFormData = () => {
  115. stockData.value = {
  116. currentStockId: '',
  117. inventoryId: '',
  118. inventoryNo: '',
  119. inventoryName: '',
  120. inventoryType: '',
  121. quantity: '',
  122. batchNo: '',
  123. transferName: '',
  124. transferId: '',
  125. transferNo: '',
  126. positionId: '',
  127. positionNo: '',
  128. positionName: '',
  129. outAll: true,
  130. };
  131. imageUrl.value = '';
  132. photoFile.value = null;
  133. transferPositionSelector.value.clearSelected();
  134. usingPositionSelector.value.clearSelected();
  135. };
  136. // 提交出库
  137. const submit = () => {
  138. if (!stockData.value.inventoryNo) {
  139. showFailToast({ duration: 4000, message: '请先拍摄并识别图片确认出库信息后再提交' });
  140. return;
  141. }
  142. if (!stockData.value.quantity) {
  143. showFailToast('请输入出库数量');
  144. return;
  145. }
  146. if (!stockData.value.transferId) {
  147. showFailToast('请选择中转区货位');
  148. return;
  149. }
  150. if (!stockData.value.positionId) {
  151. showFailToast('请选择出库货位');
  152. return;
  153. }
  154. showConfirmDialog({
  155. title: '确认要出库吗?',
  156. message: '如果确认要出库,请点击【确认】按钮,否则点击【取消】按钮。',
  157. })
  158. .then(() => {
  159. submitStockOut();
  160. })
  161. .catch(() => {
  162. console.log('取消');
  163. });
  164. };
  165. // 上传图片获取出库信息
  166. const uploadImage = async () => {
  167. if (!photoFile.value) {
  168. showFailToast('请先拍摄图片后再进行识别');
  169. return;
  170. }
  171. const url = 'http://127.0.0.1:10020/AppApi/OcrResource';
  172. const fileData = new FormData();
  173. fileData.append('image', photoFile.value);
  174. showFullscreenLoading();
  175. try {
  176. const response = await fetch(url, {
  177. method: 'POST',
  178. body: fileData,
  179. });
  180. if (response.ok) {
  181. const result = await response.json();
  182. if (result.errorCode === 0) {
  183. if (result.recognizeResult) {
  184. // stockData.value = { ...result.recognizeResult };
  185. getInfo(result.recognizeResult.no, result.recognizeResult.batchNo);
  186. }
  187. } else {
  188. showFailToast({ duration: 4000, message: result.errorMessage });
  189. }
  190. hideFullscreenLoading();
  191. } else {
  192. hideFullscreenLoading();
  193. showFailToast(`识别失败 (${response.status}): ${response.statusText}`);
  194. }
  195. } catch (error) {
  196. hideFullscreenLoading();
  197. showFailToast('识别图片失败: 请调整角度后重新拍摄');
  198. }
  199. };
  200. // 获取出库物料详情
  201. const getInfo = (no, batchNo) => {
  202. showFullscreenLoading();
  203. const url = `/api/StockOutResource/queryByInventoryNoAndBatchNo?no=${no}&batchNo=${batchNo}`;
  204. ajaxApiGet(url).then(
  205. success => {
  206. const { errorCode, errorMessage, data } = success;
  207. if (errorCode === 0) {
  208. if (data) {
  209. stockData.value = { ...stockData.value, ...data };
  210. showSuccessToast('获取信息成功。');
  211. }
  212. } else {
  213. showFailToast({ duration: 5000, message: errorMessage });
  214. }
  215. hideFullscreenLoading();
  216. },
  217. error => {
  218. hideFullscreenLoading();
  219. processException(error);
  220. },
  221. );
  222. };
  223. // 提交API
  224. const submitStockOut = () => {
  225. const url = '/api/StockOutResource/scanGeneratorStockOut';
  226. const params = JSON.parse(JSON.stringify(stockData.value));
  227. delete params.workDate;
  228. ajaxApiPost(url, params).then(
  229. success => {
  230. if (success.errorCode === 0) {
  231. showSuccessToast('出库成功');
  232. clearFormData();
  233. } else {
  234. showFailToast(success.errorMessage);
  235. }
  236. },
  237. error => {
  238. processException(error);
  239. },
  240. );
  241. };
  242. // 根据货位、物料、批号查询库存
  243. const getStockOutInfo = info => {
  244. const positionId = info.id;
  245. const inventoryId = stockData.value.inventoryId;
  246. const url = `/api/StockOutResource/queryByInventoryAndPosition?positionId=${positionId}&inventoryId=${inventoryId}`;
  247. ajaxApiGet(url).then(
  248. success => {
  249. const { errorCode, errorMessage, data } = success;
  250. if (errorCode === 0) {
  251. console.log(data, '根据货位、物料、批号查询库存');
  252. if (data) {
  253. stockData.value = { ...stockData.value, ...data };
  254. }
  255. } else {
  256. showFailToast(errorMessage);
  257. }
  258. },
  259. error => {
  260. processException(error);
  261. },
  262. );
  263. };
  264. </script>
  265. <style scoped>
  266. .content {
  267. margin-top: 10px;
  268. }
  269. .scan-btn {
  270. margin: 0 10px;
  271. }
  272. .custom-picker {
  273. display: flex !important;
  274. flex-direction: column !important;
  275. height: 100% !important;
  276. }
  277. .picker-header {
  278. display: flex !important;
  279. justify-content: space-between !important;
  280. align-items: center !important;
  281. padding: 10px 16px !important;
  282. border-bottom: 1px solid #ebedf0 !important;
  283. }
  284. .picker-title {
  285. font-size: 16px !important;
  286. font-weight: 500 !important;
  287. }
  288. .picker-content {
  289. flex: 1 !important;
  290. overflow-y: auto !important;
  291. }
  292. .picker-footer {
  293. padding: 10px 16px !important;
  294. border-top: 1px solid #ebedf0 !important;
  295. display: flex !important;
  296. flex-direction: column !important;
  297. gap: 8px !important;
  298. }
  299. .loading-more {
  300. text-align: center !important;
  301. color: #969799 !important;
  302. padding: 10px 0 !important;
  303. }
  304. </style>