StockInPhoto.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  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.no" name="no" label="物料编号:" :readonly="true" />
  19. <van-field v-model="stockData.name" name="name" label="物料名称:" :readonly="true" />
  20. <van-field v-model="stockData.type" name="type" label="规格型号:" :readonly="true" />
  21. <van-field v-model="stockData.batchNo" name="batchNo" label="批号:" placeholder="点击输入批号" />
  22. <van-field v-model="stockData.num" name="num" label="生产数量:" placeholder="点击输入生产数量" />
  23. <van-field
  24. v-model="stockData.transferName" is-link readonly name="transfer" label="中转区货位:"
  25. placeholder="点击选择中转区货位" @click="isShowTransfer = true"
  26. />
  27. <van-field
  28. v-model="stockData.idleName" is-link readonly name="warehouse" label="入库货位:" placeholder="点击选择入库货位"
  29. @click="isShowIdle = true"
  30. />
  31. <div style="margin: 16px">
  32. <van-button round block type="primary" @click="submit">
  33. 提交
  34. </van-button>
  35. </div>
  36. </van-form>
  37. </div>
  38. <div>
  39. <position-selector v-model:show="isShowTransfer" position-type="transfer" @confirm="onTransferPositionSelected" />
  40. <position-selector v-model:show="isShowIdle" position-type="idle" @confirm="onIdlePositionSelected" />
  41. </div>
  42. </template>
  43. <script setup>
  44. import { ref } from 'vue';
  45. import { useRouter } from 'vue-router';
  46. import PositionSelector from './PositionSelector.vue';
  47. import { processException } from '../common/Common.js';
  48. import { ajaxApiGet, ajaxApiPost } from '../common/utils';
  49. import { showSuccessToast, showFailToast, showConfirmDialog } from 'vant';
  50. import { showFullscreenLoading, hideFullscreenLoading } from '../common/loading';
  51. const router = useRouter();
  52. const imageUrl = ref('');
  53. const fileInput = ref(null);
  54. const photoFile = ref(null);
  55. // 状态
  56. const isShowTransfer = ref(false);
  57. const isShowIdle = ref(false);
  58. const stockData = ref({
  59. no: '',
  60. name: '',
  61. type: '',
  62. num: '',
  63. batchNo: '',
  64. transferId: '',
  65. transferName: '',
  66. transferNo: '',
  67. idleId: '',
  68. idleName: '',
  69. idleNo: '',
  70. });
  71. const goBack = () => {
  72. router.back();
  73. };
  74. const takePhoto = () => {
  75. fileInput.value.click();
  76. };
  77. // 处理文件选择/拍照结果
  78. const onFileChange = event => {
  79. const file = event.target.files[0];
  80. if (!file) return;
  81. imageUrl.value = URL.createObjectURL(file);
  82. photoFile.value = file;
  83. };
  84. // 处理中转区货位选择结果
  85. const onTransferPositionSelected = item => {
  86. stockData.value.transferId = item.id;
  87. stockData.value.transferNo = item.no;
  88. stockData.value.transferName = item.name;
  89. };
  90. // 处理入库货位选择结果
  91. const onIdlePositionSelected = item => {
  92. stockData.value.idleId = item.id;
  93. stockData.value.idleNo = item.no;
  94. stockData.value.idleName = item.name;
  95. };
  96. // 清除表单数据
  97. const clearFormData = () => {
  98. stockData.value = {
  99. id: '',
  100. no: '',
  101. name: '',
  102. type: '',
  103. num: '',
  104. batchNo: '',
  105. transferId: '',
  106. transferName: '',
  107. transferNo: '',
  108. idleId: '',
  109. idleName: '',
  110. idleNo: '',
  111. };
  112. imageUrl.value = '';
  113. photoFile.value = null;
  114. };
  115. // 提交入库
  116. const submit = () => {
  117. if (!stockData.value.no) {
  118. showFailToast({ duration: 4000, message: '请先拍摄并识别图片确认入库信息后再提交' });
  119. return;
  120. }
  121. if (!stockData.value.num) {
  122. showFailToast('请输入入库数量');
  123. return;
  124. }
  125. if (!stockData.value.transferId) {
  126. showFailToast('请选择中转区货位');
  127. return;
  128. }
  129. if (!stockData.value.idleId) {
  130. showFailToast('请选择入库货位');
  131. return;
  132. }
  133. showConfirmDialog({
  134. title: '确认要入库吗?',
  135. message: '如果确认要入库,请点击【确认】按钮,否则点击【取消】按钮。',
  136. })
  137. .then(() => {
  138. submitStockIn();
  139. })
  140. .catch(() => {
  141. console.log('取消');
  142. });
  143. };
  144. // 上传图片获取入库信息
  145. const uploadImage = async () => {
  146. if (!photoFile.value) {
  147. showFailToast('请先拍摄图片后再进行识别');
  148. return;
  149. }
  150. const url = 'http://192.168.1.109:10020/AppApi/OcrResource';
  151. const fileData = new FormData();
  152. fileData.append('image', photoFile.value);
  153. showFullscreenLoading();
  154. try {
  155. const response = await fetch(url, {
  156. method: 'POST',
  157. body: fileData,
  158. });
  159. if (response.ok) {
  160. const result = await response.json();
  161. if (result.errorCode === 0) {
  162. if (result.recognizeResult) {
  163. stockData.value = { ...result.recognizeResult };
  164. getInfo(result.recognizeResult.no);
  165. }
  166. } else {
  167. showFailToast({ duration: 4000, message: result.errorMessage });
  168. }
  169. hideFullscreenLoading();
  170. } else {
  171. hideFullscreenLoading();
  172. showFailToast(`识别失败 (${response.status}): ${response.statusText}`);
  173. }
  174. } catch (error) {
  175. hideFullscreenLoading();
  176. showFailToast('识别图片失败: 请调整角度后重新拍摄');
  177. }
  178. };
  179. // 获取入库物料详情
  180. const getInfo = no => {
  181. showFullscreenLoading();
  182. const url = `/api/InventoryResource/queryStockInInventory?start=0&length=1&filter=${no}`;
  183. ajaxApiGet(url).then(
  184. success => {
  185. const { errorCode, errorMessage, datas } = success;
  186. if (errorCode === 0) {
  187. if (datas && datas.length) {
  188. stockData.value = { ...stockData.value, ...datas[0] };
  189. showSuccessToast({ duration: 5000, message: '获取信息成功,请先确认生产数量是否准确,若不准确请先修改数量。' });
  190. }
  191. } else {
  192. showFailToast({ duration: 5000, message: errorMessage });
  193. }
  194. hideFullscreenLoading();
  195. },
  196. error => {
  197. hideFullscreenLoading();
  198. processException(error);
  199. },
  200. );
  201. };
  202. // 提交API
  203. const submitStockIn = () => {
  204. const url = '/api/stockInResource/scanGeneratorStockIn';
  205. const params = JSON.parse(JSON.stringify(stockData.value));
  206. delete params.workDate;
  207. ajaxApiPost(url, params).then(
  208. success => {
  209. if (success.errorCode === 0) {
  210. showSuccessToast('入库成功');
  211. clearFormData();
  212. } else {
  213. showFailToast(success.errorMessage);
  214. }
  215. },
  216. error => {
  217. processException(error);
  218. },
  219. );
  220. };
  221. </script>
  222. <style scoped>
  223. .content {
  224. margin-top: 10px;
  225. }
  226. .scan-btn {
  227. margin: 0 10px;
  228. }
  229. .custom-picker {
  230. display: flex !important;
  231. flex-direction: column !important;
  232. height: 100% !important;
  233. }
  234. .picker-header {
  235. display: flex !important;
  236. justify-content: space-between !important;
  237. align-items: center !important;
  238. padding: 10px 16px !important;
  239. border-bottom: 1px solid #ebedf0 !important;
  240. }
  241. .picker-title {
  242. font-size: 16px !important;
  243. font-weight: 500 !important;
  244. }
  245. .picker-content {
  246. flex: 1 !important;
  247. overflow-y: auto !important;
  248. }
  249. .picker-footer {
  250. padding: 10px 16px !important;
  251. border-top: 1px solid #ebedf0 !important;
  252. display: flex !important;
  253. flex-direction: column !important;
  254. gap: 8px !important;
  255. }
  256. .loading-more {
  257. text-align: center !important;
  258. color: #969799 !important;
  259. padding: 10px 0 !important;
  260. }
  261. </style>