speedy-check.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. <template>
  2. <view class="container" style="padding-top: 44px; padding-bottom: 10px">
  3. <u-navbar
  4. title="快速盘点"
  5. :border="false"
  6. fixed
  7. safe-area-inset-top
  8. bgColor="#22ac38"
  9. titleStyle="font-size:36rpx;color:#fff"
  10. >
  11. <template #left>
  12. <u-icon name="arrow-left" color="#fff" size="20" @click="goBack"></u-icon>
  13. </template>
  14. <template #right>
  15. <text style="color: #ffffff" @click="onSubmit" v-permission="'app:wms:speedyCheck:confirm'">提交</text>
  16. </template>
  17. </u-navbar>
  18. <!-- 盘点信息选择区域 -->
  19. <view class="select-area" style="margin-top: 44px">
  20. <u-cell-group :border="true">
  21. <u-cell
  22. title="盘点方式"
  23. :value="checkMethod"
  24. @click="showCheckMethodPicker = true"
  25. :isLink="true"
  26. :border="true"
  27. />
  28. <u-cell
  29. title="目标库位"
  30. :value="location"
  31. @click="handleLocationSelect"
  32. :isLink="true"
  33. :border="true"
  34. />
  35. </u-cell-group>
  36. </view>
  37. <!-- 盘点方式选择器 -->
  38. <u-picker
  39. :show="showCheckMethodPicker"
  40. :columns="[checkMethodOptions]"
  41. @confirm="onCheckMethodConfirm"
  42. @cancel="showCheckMethodPicker = false"
  43. :showToolbar="true"
  44. title="选择盘点方式"
  45. :closeOnClickOverlay="true"
  46. cancelText="取消"
  47. confirmText="确定"
  48. :itemHeight="44"
  49. :visibleItemCount="5"
  50. />
  51. <!-- 库位选择弹出层 -->
  52. <u-popup
  53. :show="showLocationPopup"
  54. mode="bottom"
  55. @close="close"
  56. @open="open"
  57. :closeOnClickOverlay="true"
  58. :round="10"
  59. :safeAreaInsetBottom="true"
  60. >
  61. <view class="location-popup">
  62. <view class="location-popup-header">
  63. <text style="flex: 1">选择库位</text>
  64. <u-text
  65. style="width: 40px; flex: none"
  66. type="primary"
  67. text="确定"
  68. @click="onLocationConfirm"
  69. ></u-text>
  70. </view>
  71. <view class="location-popup-content">
  72. <u-input
  73. v-model="selectedLocation"
  74. placeholder="请输入库位"
  75. customStyle="background-color: #f6f6f6; border-radius: 4px;"
  76. ></u-input>
  77. <view class="location-list">
  78. <view
  79. class="location-item"
  80. v-for="(item, index) in locationList"
  81. :key="index"
  82. @click="selectLocation(item)"
  83. :class="{ 'location-item--selected': selectedLocation === item }"
  84. >
  85. <text>{{ item }}</text>
  86. <u-icon
  87. v-if="selectedLocation === item"
  88. name="checkmark"
  89. color="#19be6b"
  90. size="16"
  91. ></u-icon>
  92. </view>
  93. </view>
  94. </view>
  95. </view>
  96. </u-popup>
  97. <!-- 订单列表 -->
  98. <view class="product-details">
  99. <LocationOrderItem v-for="(item, index) in products" :isLink :key="index" :item="item" />
  100. </view>
  101. <view class="add-btn" @click="handleAdd">
  102. <u-icon name="plus-circle" size="40" color="#19be6b"></u-icon>
  103. </view>
  104. <!-- 底部扫码输入框 -->
  105. <view class="fixed-bottom pad-20" style="background: #ffffff">
  106. <u-search
  107. placeholder="请输入快递单号/订单编号"
  108. v-model="searchValue"
  109. @search="onSearch"
  110. :show-action="false"
  111. custom-style="margin-right:10px"
  112. ></u-search>
  113. <u-icon name="scan" size="28" color="#19be6b" @click="openScan"></u-icon>
  114. </view>
  115. </view>
  116. </template>
  117. <script setup>
  118. import { reactive, ref } from "vue";
  119. import { onLoad, onUnload, onShow } from "@dcloudio/uni-app";
  120. import LocationOrderItem from "./components/LocationOrderItem.vue";
  121. const goBack = () => {
  122. uni.navigateBack();
  123. };
  124. // 盘点方式相关
  125. const showCheckMethodPicker = ref(false);
  126. const checkMethod = ref("增加数量");
  127. const resetType = ref(0);
  128. const checkMethodOptions = ["增加数量", "减少数量", "实际数量"];
  129. //获取用户的默认仓库
  130. const getUserDefaultWarehouse = () => {
  131. uni.$u.http.get("/app/appUser/getUserBindGodown").then((res) => {
  132. if (res.code == 200) {
  133. getWarehouseLocationList(res.data.id);
  134. }
  135. });
  136. };
  137. // 库位相关
  138. const location = ref("");
  139. const showLocationPopup = ref(false);
  140. const locationList = ref([]);
  141. const selectedLocation = ref("");
  142. //根据仓库获取库位列表
  143. const godownId = ref();
  144. const getWarehouseLocationList = (id) => {
  145. godownId.value = id;
  146. uni.$u.http.get("/app/stock/getGodownPosition?godownId=" + id).then((res) => {
  147. if (res.code == 200) {
  148. locationList.value = res.data;
  149. }
  150. });
  151. };
  152. // 其他数据
  153. const searchValue = ref("");
  154. const products = ref([]);
  155. // 方法
  156. const onCheckMethodConfirm = (e) => {
  157. checkMethod.value = e.value[0];
  158. showCheckMethodPicker.value = false;
  159. resetType.value = e.indexs[0] + 1;
  160. };
  161. const handleLocationSelect = () => {
  162. showLocationPopup.value = true;
  163. selectedLocation.value = location.value;
  164. };
  165. const onLocationConfirm = () => {
  166. if (selectedLocation.value) {
  167. location.value = selectedLocation.value;
  168. showLocationPopup.value = false;
  169. selectedLocation.value = "";
  170. } else {
  171. uni.$u.toast("请选择库位");
  172. }
  173. };
  174. const selectLocation = (item) => {
  175. selectedLocation.value = item;
  176. };
  177. //盘点提交
  178. const onSubmit = () => {
  179. if (products.value.length === 0) {
  180. uni.$u.toast("请添加盘点订单");
  181. return;
  182. }
  183. // 构建请求参数
  184. const params = {
  185. godownId: godownId.value,
  186. positionCode: location.value,
  187. orderInfo: products.value.map((item) => ({
  188. orderId: item.orderId,
  189. waybillCode: item.waybillCode,
  190. bookNum: item.badNum,
  191. remark: item.remark || "",
  192. })),
  193. resetType: resetType.value,
  194. };
  195. // 调用重置库存API
  196. uni.$u.http
  197. .post("/app/stock/resetStock", params)
  198. .then((res) => {
  199. if (res.code === 200) {
  200. uni.$u.toast("盘点成功");
  201. location.value = "";
  202. products.value = [];
  203. searchValue.value = "";
  204. } else {
  205. uni.$u.toast(res.msg || "提交失败");
  206. }
  207. })
  208. .catch((err) => {
  209. uni.$u.toast("提交失败");
  210. console.error(err);
  211. });
  212. };
  213. const onSearch = () => {
  214. if (!searchValue.value) {
  215. uni.$u.toast("请输入快递单号或订单编号");
  216. return;
  217. }
  218. // 判断搜索类型:纯数字为订单号(searchType=1),字母+数字组合为物流单号(searchType=2)
  219. const searchType = /^[0-9]+$/.test(searchValue.value) ? 1 : 2;
  220. // 调用接口获取订单数据
  221. uni.$u.http
  222. .get("/app/stock/searchBadOrderForReset", {
  223. params: {
  224. searchType,
  225. search: searchValue.value,
  226. },
  227. })
  228. .then((res) => {
  229. if (res.code === 200) {
  230. // 检查是否已存在相同订单
  231. const exists = products.value.some(
  232. (item) => item.orderId === res.data.orderId || item.waybillCode === res.data.waybillCode
  233. );
  234. if (exists) {
  235. uni.$u.toast("该订单已添加");
  236. return;
  237. }
  238. // 添加新订单到列表
  239. products.value.push(res.data);
  240. searchValue.value = ""; // 清空搜索框
  241. } else {
  242. uni.$u.toast(res.msg || "查询失败");
  243. }
  244. })
  245. .catch((err) => {
  246. uni.$u.toast("查询失败");
  247. console.error(err);
  248. });
  249. };
  250. //扫码后判断是否是库位还是 订单号
  251. const dealScanText = (scanText) => {
  252. let isLocation = scanText.indexOf("-") > -1 && scanText.length == 9;
  253. if (isLocation) {
  254. if (locationList.value.includes(scanText)) {
  255. location.value = scanText;
  256. } else {
  257. uni.$u.toast("库位不存在");
  258. }
  259. } else {
  260. searchValue.value = scanText;
  261. onSearch();
  262. }
  263. };
  264. const openScan = () => {
  265. // #ifdef APP-PLUS || MP-WEIXIN
  266. uni.scanCode({
  267. success: (res) => {
  268. dealScanText(res.result);
  269. },
  270. fail: (err) => {
  271. uni.showToast({
  272. title: "扫码失败",
  273. icon: "error",
  274. });
  275. },
  276. });
  277. // #endif
  278. };
  279. function handleAdd() {
  280. if (!location.value) {
  281. uni.$u.toast("请选择库位");
  282. return;
  283. }
  284. uni.navigateTo({
  285. url: "/pages/index/wms/speedy-check-add?location=" + location.value,
  286. });
  287. }
  288. const close = () => {
  289. showLocationPopup.value = false;
  290. };
  291. const open = () => {
  292. showLocationPopup.value = true;
  293. };
  294. onLoad(() => {
  295. getUserDefaultWarehouse();
  296. uni.$on("selectedProducts", (list) => {
  297. products.value = list;
  298. });
  299. // #ifdef APP-PLUS
  300. uni.$u.useGlobalEvent((e) => {
  301. if (e.barcode) {
  302. dealScanText(e.barcode);
  303. }
  304. });
  305. // #endif
  306. });
  307. onShow(() => {
  308. uni.$u.updateActivePageOnShow()
  309. })
  310. onUnload(() => {
  311. uni.$off("selectedProducts");
  312. });
  313. </script>
  314. <style lang="scss" scoped>
  315. .select-area {
  316. background-color: #fff;
  317. }
  318. .product-details {
  319. margin-bottom: 120rpx; // 为底部搜索框留出空间
  320. }
  321. .fixed-bottom {
  322. position: fixed;
  323. bottom: 0;
  324. left: 0;
  325. right: 0;
  326. display: flex;
  327. align-items: center;
  328. padding: 20rpx;
  329. background: #ffffff;
  330. box-shadow: 0 -2rpx 6rpx rgba(0, 0, 0, 0.1);
  331. }
  332. .add-btn {
  333. position: fixed;
  334. right: 0;
  335. bottom: 30%;
  336. z-index: 99;
  337. cursor: pointer;
  338. }
  339. .location-input-wrapper {
  340. display: flex;
  341. align-items: center;
  342. justify-content: flex-end;
  343. }
  344. .location-popup {
  345. background-color: #fff;
  346. padding: 20rpx;
  347. &-header {
  348. display: flex;
  349. justify-content: space-between;
  350. align-items: center;
  351. padding-bottom: 24rpx;
  352. border-bottom: 1px solid #eee;
  353. position: relative;
  354. text {
  355. font-size: 32rpx;
  356. font-weight: 500;
  357. flex: 1;
  358. }
  359. .u-button {
  360. position: absolute;
  361. right: 20rpx;
  362. }
  363. }
  364. &-content {
  365. padding: 20rpx 0;
  366. max-height: 600rpx;
  367. overflow-y: auto;
  368. }
  369. }
  370. .location-list {
  371. margin-top: 20rpx;
  372. max-height: 400rpx;
  373. overflow-y: auto;
  374. }
  375. .location-item {
  376. padding: 20rpx;
  377. border-bottom: 1px solid #eee;
  378. display: flex;
  379. align-items: center;
  380. justify-content: space-between;
  381. &--selected {
  382. color: #19be6b;
  383. }
  384. }
  385. </style>